The Driver SDK is a library that you integrate into your driver app. It is responsible for updating the Fleet Engine with the driver’s location, route, distance remaining, and ETA. It also integrates with the Navigation SDK, which provides turn-by-turn navigation instructions for the driver.
Minimum system requirements
Prerequisites
This guide assumes your app already implements the Navigation SDK and that the Fleet Engine backend is set up and available. However, the example code provides a sample of how to set up the Navigation SDK.
You must also enable the Maps SDK for iOS in your Google Cloud Project and Get an API Key.
Get access
If you are a Google Workspace customer, create a
Workspace Group such as
google-maps-platform-sdk-users@workspacedomain.com
during onboarding and
provide the name to Google. This is the recommended approach.
Your Workspace Group will then be added to an allowlist that
grants access to the correct CocoaPods repositories. Confirm that the user
emails and service account emails that need access are included in this list.
If your organization cannot create Workspace Groups, send to Google a list of user and service account emails that need access to these artifacts.
Local development
For local development, it is sufficient to log in with the Cloud SDK.
gcloud
gcloud auth login
The email used to log in must be a member of the Workspace Group.
Automation (build systems or continuous integration)
Set up your automation hosts according to best practices:
If your process runs inside a Google Cloud environment, use automatic credential detection.
Otherwise, store the service account key file in a secure location on the host's file-system and set the GOOGLE_APPLICATION_CREDENTIALS environment variable appropriately.
The service account email associated with the credentials must be a member of the Workspace Goup.
Project Configuration
You can configure the Driver SDK using CocoaPods.
Use CocoaPods
To configure the Driver SDK using CocoaPods, you need the following items:
- The CocoaPods tool: To install this tool, open the Terminal and run the
following command.
shell sudo gem install cocoapods
Refer to the CocoaPods Getting Started guide for more details.
Create a Podfile for the Driver SDK and use it to install the API and its dependencies: Create a file named Podfile in your project directory. This file defines your project's dependencies. Edit the Podfile and add your dependencies. Here is an example which includes the dependencies:
source "https://github.com/CocoaPods/Specs.git" target 'YOUR_APPLICATION_TARGET_NAME_HERE' do pod 'GoogleRidesharingDriver' end
Save the Podfile. Open a terminal and go to the directory containing the Podfile:
cd <path-to-project>
Run the pod install command. This will install the APIs specified in the Podfile, along with any dependencies they may have.
pod install
Close Xcode, and then open (double-click) your project's .xcworkspace file to launch Xcode. From this time onwards, you must use the .xcworkspace file to open the project.
Alpha/Beta SDK versions
To configure the Alpha or Beta versions of the Driver SDK for iOS you need the following items:
The CocoaPods tool: To install this tool, open the Terminal and run the following command.
sudo gem install cocoapods
Refer to the CocoaPods Getting Started guide for more details.
Your development account on the Google access list. The pod repository of the Alpha and Beta versions of the SDK are not public source. To access those versions, contact the Google Customer Engineer. The engineer adds your development account to the access list and then sets a cookie for authentication.
After your project is on the access list, you can access the pod.
Create a Podfile for the Driver SDK for iOS and use it to install the API and its dependencies: Create a file named Podfile in your project directory. This file defines your project's dependencies. Edit the Podfile and add your dependencies. Here is an example which includes the dependencies:
source "https://cpdc-eap.googlesource.com/ridesharing-driver-sdk.git" source "https://github.com/CocoaPods/Specs.git" target 'YOUR_APPLICATION_TARGET_NAME_HERE' do pod 'GoogleRidesharingDriver' end
Save the Podfile. Open a terminal and go to the directory containing the Podfile:
cd <path-to-project>
Run the pod install command. This will install the APIs specified in the Podfile, along with any dependencies they may have.
pod install
Close Xcode, and then open (double-click) your project's .xcworkspace file to launch Xcode. From this time onwards, you must use the .xcworkspace file to open the project.
Implement authorization and authentication
When your Driver app generates and sends updates to the Fleet Engine backend,
the requests must include valid access tokens. To authorize and
authenticate these requests, the Driver SDK calls your object conforming to
the GMTDAuthorization
protocol. The object is responsible for providing the required access token.
As the app developer, you choose how tokens are generated. Your implementation should provide the ability to do the following:
- Fetch an access token, possibly in JSON format, from an HTTPS server.
- Parse and cache the token.
- Refresh the token when it expires.
For details of the tokens expected by the Fleet Engine server, see Creating a JSON Web Token (JWT) for authorization.
The provider ID is the same as Google Cloud Project ID. See Fleet Engine Deliveries API User Guide for more information.
The following example implements an access token provider:
#import "SampleAccessTokenProvider.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
// SampleAccessTokenProvider.h
@interface SampleAccessTokenProvider : NSObject<GMTDAuthorization>
@end
static NSString *const PROVIDER_URL = @"INSERT_YOUR_TOKEN_PROVIDER_URL";
// SampleAccessTokenProvider.m
@implementation SampleAccessTokenProvider{
// The cached vehicle token.
NSString *_cachedVehicleToken;
// Keep track of the vehicle ID the cached token is for.
NSString *_lastKnownVehicleID;
// Keep track of when tokens expire for caching.
NSTimeInterval _tokenExpiration;
}
- (void)fetchTokenWithContext:(nullable GMTDAuthorizationContext *)authorizationContext
completion:(nonnull GMTDAuthTokenFetchCompletionHandler)completion {
if (!completion) {
NSAssert(NO, @"%s encountered an unexpected nil completion.", __PRETTY_FUNCTION__);
return;
}
// Get the vehicle ID from the authorizationContext. This is set by the Driver SDK.
NSString *vehicleID = authorizationContext.vehicleID;
if (!vehicleID) {
NSAssert(NO, @"Vehicle ID is missing from authorizationContext.");
return;
}
// Clear cached vehicle token if vehicle ID has changed.
if (![_lastKnownVehicleID isEqual:vehicleID]) {
_tokenExpiration = 0.0;
_cachedVehicleToken = nil;
}
_lastKnownVehicleID = vehicleID;
// Clear cached vehicle token if it has expired.
if ([[NSDate date] timeIntervalSince1970] > _tokenExpiration) {
_cachedVehicleToken = nil;
}
// If appropriate, use the cached token.
if (_cachedVehicleToken) {
completion(_cachedVehicleToken, nil);
return;
}
// Otherwise, try to fetch a new token from your server.
NSURL *requestURL = [NSURL URLWithString:PROVIDER_URL];
NSMutableURLRequest *request =
[[NSMutableURLRequest alloc] initWithURL:requestURL];
request.HTTPMethod = @"GET";
// Replace the following key values with the appropriate keys based on your
// server's expected response.
NSString *vehicleTokenKey = @"VEHICLE_TOKEN_KEY";
NSString *tokenExpirationKey = @"TOKEN_EXPIRATION";
__weak typeof(self) weakSelf = self;
void (^handler)(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable error) =
^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
typeof(self) strongSelf = weakSelf;
if (error) {
completion(nil, error);
return;
}
NSError *JSONError;
NSMutableDictionary *JSONResponse =
[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&JSONError];
if (JSONError) {
completion(nil, JSONError);
return;
} else {
// Sample code only. No validation logic.
id expirationData = JSONResponse[tokenExpirationKey];
if ([expirationData isKindOfClass:[NSNumber class]]) {
NSTimeInterval expirationTime = ((NSNumber *)expirationData).doubleValue;
strongSelf->_tokenExpiration = [[NSDate date] timeIntervalSince1970] + expirationTime;
}
strongSelf->_cachedVehicleToken = JSONResponse[vehicleTokenKey];
completion(JSONResponse[vehicleTokenKey], nil);
}
};
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *mainQueueURLSession =
[NSURLSession sessionWithConfiguration:config delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [mainQueueURLSession dataTaskWithRequest:request completionHandler:handler];
[task resume];
}
@end
Create a DeliveryDriverAPI instance
To get a GMTDDeliveryVehicleReporter
instance, you first need to create a
GMTDDeliveryDriverAPI
instance using the providerID,
vehicleID, driverContext and accessTokenProvider. The providerID is the same as
Google Cloud Project ID. And you can access the GMTDDeliveryVehicleReporter
instance from the driver API directly.
The following example creates a GMTDDeliveryDriverAPI
instance:
#import “SampleViewController.h”
#import “SampleAccessTokenProvider.h”
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
@implementation SampleViewController {
GMSMapView *_mapView;
}
- (void)viewDidLoad {
NSString *vehicleID = @"INSERT_CREATED_VEHICLE_ID";
SampleAccessTokenProvider *accessTokenProvider =
[[SampleAccessTokenProvider alloc] init];
GMTDDriverContext *driverContext =
[[GMTDDriverContext alloc] initWithAccessTokenProvider:accessTokenProvider
providerID:PROVIDER_ID
vehicleID:vehicleID
navigator:_mapView.navigator];
GMTDDeliveryDriverAPI *deliveryDriverAPI = [[GMTDDeliveryDriverAPI alloc] initWithDriverContext:driverContext];
}
Optionally listen to VehicleReporter events
GMTDDeliveryVehicleReporter
periodically updates the vehicle when
locationTrackingEnabled
is YES. To respond to these periodic updates, any
object can subscribe to GMTDDeliveryVehicleReporter
events by conforming to
theGMTDVehicleReporterListener
protocol.
You can handle the following events:
vehicleReporter:didSucceedVehicleUpdate
Informs the Driver app that the backend services successfully received the vehicle location and state update.
vehicleReporter:didFailVehicleUpdate:withError
Informs the listener that a vehicle update failed. As long as location tracking is enabled,
GMTDDeliveryVehicleReporter
continues to send the latest data to Fleet Engine backend.
The following example handles these events:
SampleViewController.h
@interface SampleViewController : UIViewController<GMTDVehicleReporterListener>
@end
SampleViewController.m
#import “SampleViewController.h”
#import “SampleAccessTokenProvider.h”
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
@implementation SampleViewController {
GMSMapView *_mapView;
}
- (void)viewDidLoad {
// ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
[ridesharingDriverAPI.vehicleReporter addListener:self];
}
- (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didSucceedVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate {
// Handle update succeeded.
}
- (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate withError:(NSError *)error {
// Handle update failed.
}
@end
Enable location tracking
To enable location tracking, your app can set locationTrackingEnabled
to YES
on GMTDDeliveryVehicleReporter
. Then GMTDDeliveryVehicleReporter
will =
automatically send location updates. When the GMSNavigator
is in navigation
mode (when a destination is set through setDestinations
) and
locationTrackingEnabled
is set to YES
, GMTDDeliveryVehicleReporter
will
automatically send route and ETA updates as well.
The route set during those updates will be the same route the driver is
navigating to during the navigation session. Thus, for shipment tracking to work
properly, the waypoint set through -setDestinations:callback:
should match the
destination set in the Fleet Engine backend.
The following example enables location tracking:
SampleViewController.m
#import “SampleViewController.h”
#import “SampleAccessTokenProvider.h”
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
@implementation SampleViewController {
GMSMapView *_mapView;
}
- (void)viewDidLoad {
// ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
deliveryDriverAPI.vehicleReporter.locationTrackingEnabled = YES;
}
@end
By default, the reporting interval is 10 seconds, but the reporting interval can
be changed with locationUpdateInterval
. The minimum supported update interval
is 5 seconds. The maximum supported update interval is 60 seconds. More frequent
updates may result in slower requests and errors.
Disable location updates
Your app can disable location updates for a vehicle. For example, when a
driver's shift ends, your app can set locationTrackingEnabled
to NO
.
_vehicleReporter.locationTrackingEnabled = NO
Handle update_mask errors
When GMTDDeliveryVehicleReporter
sends a vehicle update, an update_mask
error can occur when the mask is empty, and it typically occurs for the first
update after startup. The following example shows how to handle this error:
Swift
import GoogleRidesharingDriver
class VehicleReporterListener: NSObject, GMTDVehicleReporterListener {
func vehicleReporter(
_ vehicleReporter: GMTDVehicleReporter,
didFail vehicleUpdate: GMTDVehicleUpdate,
withError error: Error
) {
let fullError = error as NSError
if let innerError = fullError.userInfo[NSUnderlyingErrorKey] as? NSError {
let innerFullError = innerError as NSError
if innerFullError.localizedDescription.contains("update_mask cannot be empty") {
emptyMaskUpdates += 1
return
}
}
failedUpdates += 1
}
override init() {
emptyMaskUpdates = 0
failedUpdates = 0
}
}
Objective-C
#import "VehicleReporterListener.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
@implementation VehicleReporterListener {
NSInteger emptyMaskUpdates = 0;
NSInteger failedUpdates = 0;
}
- (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter
didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate
withError:(NSError *)error {
for (NSError *underlyingError in error.underlyingErrors) {
if ([underlyingError.localizedDescription containsString:@"update_mask cannot be empty"]) {
emptyMaskUpdates += 1;
return;
}
}
failedUpdates += 1
}
@end