You can use the Driver SDK to provide enhanced navigation and tracking to your Trip and Order Progress application. The Driver SDK provides vehicle location and task updates to the On-demand Rides and Deliveries Solution Fleet Engine.
The Driver SDK keeps the Fleet Engine services and your custom services aware of
the vehicle's location and state. For example, the vehicle can be ONLINE
or
OFFLINE
, and the vehicle location changes as a trip progresses.
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.
Project configuration
Swift Package Manager
The Driver SDK can be installed via Swift Package Manager. To add the SDK, ensure you have removed any existing Driver SDK dependencies.
To add the SDK to a new or existing project, follow these steps:
-
Open your Xcode
project
orworkspace
, then go to File > Add Package Dependencies. - Enter https://github.com/googlemaps/ios-driver-sdk as the URL, press Enter to pull in the package, and click "Add Package".
-
To install a specific
version
, set the Dependency Rule field to one of the version-based options. For new projects, we recommend specifying the latest version and using the "Exact Version" option. Once complete, click "Add Package". -
From the Choose Package Products window, verify
GoogleRidesharingDriver
will be added to your designatedmain
target. Once complete, click "Add Package". -
To verify your installation, navigate to your target's
General
pane. In the Frameworks, Libraries, and Embedded Content you should see the installed packages. You can also view the "Package Dependencies" section of "Project Navigator" to verify the package and its version.
To update the package
for an existing project, follow these steps:
If you are upgrading from a version earlier than 9.0.0, you must remove the following dependencies:
GoogleMapsBase
,GoogleMapsCore
, andGoogleMapsM4B
after upgrading. Do not remove the dependency forGoogleMaps
. For more information, see the Version 9.0.0 Release Notes.From your Xcode project configuration settings, find Frameworks, Libraries, and Embedded Content. Use the minus sign(-) to remove the following framework:
GoogleMapsBase
(Only for upgrades from versions earlier than 9.0.0)GoogleMapsCore
(Only for upgrades from versions earlier than 9.0.0)GoogleMapsM4B
(Only for upgrades from versions earlier than 9.0.0)
- From Xcode, go to "File > Packages > Update To Latest Package Versions".
- To verify your installation, go to the Package Dependencies section of Project Navigator to verify the package and its version.
To remove existing Driver SDK dependencies added using
CocoaPods
, follow these steps:
- Close your Xcode workspace. Open terminal and execute the following command:
sudo gem install cocoapods-deintegrate cocoapods-clean pod deintegrate pod cache clean --all
-
Remove the
Podfile
,Podfile.resolved
and the Xcodeworkspace
if you are not using them for anything other than CocoaPods.
To remove the existing Driver SDK installed manually, follow these steps:
From your Xcode project configuration settings, find Frameworks, Libraries, and Embedded Content. Use the minus sign
(-)
to remove the following framework:GoogleRidesharingDriver.xcframework
From the top level directory of your Xcode project, remove the
GoogleRidesharingDriver
bundle.
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.
sudo gem install cocoapods
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
Here is an example which includes the Alpha and Beta pods for the Driver SDK as 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.
Refer to the CocoaPods Getting Started guide for more details.
Manual installation
An XCFramework is a binary package that you use to install the Driver SDK. You can use this package on multiple platforms, including machines using Apple silicon. This guide shows how to manually add the XCFramework containing the Driver SDK to your project and configure your build settings in Xcode.
Download the SDK binary and resources:
Extract the files to access the XCFramework and resources.
Start Xcode and either open an existing project, or create a new project. If you're new to iOS, create a new project and select the iOS App template.
Create a Frameworks group under your project group if one does not exist already.
To install the Driver SDK, drag the
GoogleRidesharingDriver.xcframework
file into your project under Frameworks, Libraries, and Embedded Content. When prompted, select Copy items if needed.Drag the downloaded
GoogleRidesharingDriver.bundle
into the top level directory of your Xcode project. When prompted, selectCopy items if needed
.Select your project from the Project Navigator, and choose your application's target.
Open the Build Phases tab, and in Link Binary with Libraries, add the following frameworks and libraries if they are not already present:
Accelerate.framework
AudioToolbox.framework
AVFoundation.framework
CoreData.framework
CoreGraphics.framework
CoreLocation.framework
CoreTelephony.framework
CoreText.framework
GLKit.framework
ImageIO.framework
libc++.tbd
libxml2.tbd
libz.tbd
LocalAuthentication.framework
OpenGLES.framework
QuartzCore.framework
SystemConfiguration.framework
UIKit.framework
WebKit.framework
Choose your project, rather than a specific target, and open the Build Settings tab. In the Other Linker Flags section, add
-ObjC
for both debug and release. If these settings are not visible, change the filter in the Build Settings bar from Basic to All.
Inspect Apple Privacy Manifest file
Apple requires app privacy details for apps on the App Store. Visit the Apple App Store Privacy Details page for updates and more information.
The Apple Privacy Manifest file is included in the resources bundle for the SDK. To verify that the Privacy Manifest File has been included, and to inspect its contents, create an archive of your app and generate a privacy report from the archive.
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 Quickstart Guide for more information.
The following example implements an access token provider:
Swift
import GoogleRidesharingDriver
private let providerURL = "INSERT_YOUR_TOKEN_PROVIDER_URL"
class SampleAccessTokenProvider: NSObject, GMTDAuthorization {
private struct AuthToken {
// The cached vehicle token.
let token: String
// Keep track of when the token expires for caching.
let expiration: TimeInterval
// Keep track of the vehicle ID the cached token is for.
let vehicleID: String
}
enum AccessTokenError: Error {
case missingAuthorizationContext
case missingData
}
private var authToken: AuthToken?
func fetchToken(
with authorizationContext: GMTDAuthorizationContext?,
completion: @escaping GMTDAuthTokenFetchCompletionHandler
) {
// Get the vehicle ID from the authorizationContext. This is set by the Driver SDK.
guard let authorizationContext = authorizationContext else {
completion(nil, AccessTokenError.missingAuthorizationContext)
return
}
let vehicleID = authorizationContext.vehicleID
// If appropriate, use the cached token.
if let authToken = authToken,
authToken.expiration > Date.now.timeIntervalSince1970 && authToken.vehicleID == vehicleID
{
completion(authToken.token, nil)
return
}
// Otherwise, try to fetch a new token from your server.
let request = URLRequest(url: URL(string: providerURL))
let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
guard let strongSelf = self else { return }
guard error == nil else {
completion(nil, error)
return
}
// Replace the following key values with the appropriate keys based on your
// server's expected response.
let vehicleTokenKey = "VEHICLE_TOKEN_KEY"
let tokenExpirationKey = "TOKEN_EXPIRATION"
guard let data = data,
let fetchData = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let token = fetchData[vehicleTokenKey] as? String,
let expiration = fetchData[tokenExpirationKey] as? Double
else {
completion(nil, AccessTokenError.missingData)
return
}
strongSelf.authToken = AuthToken(
token: token, expiration: expiration, vehicleID: vehicleID)
completion(token, nil)
}
task.resume()
}
}
Objective-C
#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 {
// 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 vehicletoken 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 RidesharingDriverAPI instance
To get a GMTDVehicleReporter
instance, you first need to create a
GMTDRidesharingDriverAPI
instance using the providerID, vehicleID,
driverContext and accessTokenProvider. The providerID is the same as Google
Cloud Project ID. And you can access the GMTDVehicleReporter
instance from the
driver API directly.
The following example creates a GMTDRidesharingDriverAPI
instance:
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController {
private let mapView: GMSMapView
override func viewDidLoad() {
super.viewDidLoad()
let vehicleID = "INSERT_CREATED_VEHICLE_ID"
let accessTokenProvider = SampleAccessTokenProvider()
let driverContext = GMTDDriverContext(
accessTokenProvider: accessTokenProvider,
providerID: providerID,
vehicleID: vehicleID,
navigator: mapView.navigator)
let ridesharingDriverAPI = GMTDRidesharingDriverAPI(driverContext: driverContext)
}
}
Objective-C
#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];
GMTDRidesharingDriverAPI *ridesharingDriverAPI = [[GMTDRidesharingDriverAPI alloc] initWithDriverContext:driverContext];
}
Optionally listen to VehicleReporter events
GMTDVehicleReporter
periodically updates the vehicle when
locationTrackingEnabled
is true
. To respond to these periodic updates, any
object can subscribe to GMTDVehicleReporter
events by conforming to
theGMTDVehicleReporterListener
protocol.
You can handle the following events:
vehicleReporter(_:didSucceed:)
Informs the Driver app that the backend services successfully received the vehicle location and state update.
vehicleReporter(_:didFail:withError:)
Informs the listener that a vehicle update failed. As long as location tracking is enabled,
GMTDVehicleReporter
continues to send the latest data to Fleet Engine backend.
The following example handles these events:
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController, GMTDVehicleReporterListener {
private let mapView: GMSMapView
override func viewDidLoad() {
// Assumes you have implemented the sample code up to this step.
ridesharingDriverAPI.vehicleReporter.add(self)
}
func vehicleReporter(_ vehicleReporter: GMTDVehicleReporter, didSucceed vehicleUpdate: GMTDVehicleUpdate) {
// Handle update succeeded.
}
func vehicleReporter(_ vehicleReporter: GMTDVehicleReporter, didFail vehicleUpdate: GMTDVehicleUpdate, withError error: Error) {
// Handle update failed.
}
}
Objective-C
/*
* 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 have implemented the sample code up to this step.
[ridesharingDriverAPI.vehicleReporter addListener:self];
}
- (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter didSucceedVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate {
// Handle update succeeded.
}
- (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate withError:(NSError *)error {
// Handle update failed.
}
@end
Add GMTDVehicleReporter as a listener to GMSRoadSnappedLocationProvider
In order to provide location updates to the Driver SDK, GMTDVehicleReporter
must be set as a listener to the GMSRoadSnappedLocationProvider
.
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController, GMTDVehicleReporterListener {
private let mapView: GMSMapView
override func viewDidLoad() {
// Assumes you have implemented the sample code up to this step.
if let roadSnappedLocationProvider = mapView.roadSnappedLocationProvider {
roadSnappedLocationProvider.add(ridesharingDriverAPI.vehicleReporter)
roadSnappedLocationProvider.startUpdatingLocation()
}
}
}
Objective-C
/*
* 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 have implemented the sample code up to this step.
[_mapView.roadSnappedLocationProvider addListener:ridesharingDriverAPI.vehicleReporter];
[_mapView.roadSnappedLocationProvider startUpdatingLocation];
}
@end
Enable location tracking
To enable location tracking, your app can set locationTrackingEnabled
to
true
on GMTDVehicleReporter
. GMTDVehicleReporter
automatically sends
location updates. After the services match and assign the vehicle to a trip,
GMTDVehicleReporter
sends route updates automatically when the GMSNavigator
is in navigation mode (when a destination is set through setDestinations
).
The route set during the trip update will be the same route the driver is
navigating to during the navigation session. Thus, for journey sharing to work
properly, the waypoint set through setDestinations
should match the
destination set in the Fleet Engine backend.
If locationTrackingEnabled
is set to true
, trip and vehicle updates are sent
to the Fleet Engine backend at a regular interval based on the value set for
locationUpdateInterval
. If locationTrackingEnabled
is set to false
,
updates stop and a final vehicle update request is sent to the Fleet Engine
backend to set the vehicle state to GMTDVehicleState.offline
. See
updateVehicleState
for special considerations on handling failures when locationTrackingEnabled
is set to false
.
The following example enables location tracking:
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController, GMTDVehicleReporterListener {
private let mapView: GMSMapView
override func viewDidLoad() {
// Assumes you have implemented the sample code up to this step.
ridesharingDriverAPI.vehicleReporter.locationTrackingEnabled = true
}
}
Objective-C
/*
* 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 have implemented the sample code up to this step.
ridesharingDriverAPI.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.
Update the vehicle state
The following example shows how to set the vehicle state to ONLINE
. See
updateVehicleState
for details.
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController, GMTDVehicleReporterListener {
private let mapView: GMSMapView
override func viewDidLoad() {
// Assumes you have implemented the sample code up to this step.
ridesharingDriverAPI.vehicleReporter.update(.online)
}
}
Objective-C
#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 have implemented the sample code up to this step.
[ridesharingDriverAPI.vehicleReporter
updateVehicleState:GMTDVehicleStateOnline];
}
@end
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
Disable location updates and take the vehicle offline
Your app can disable updates and take the vehicle offline. For example, when a
driver's shift ends, your app can set locationTrackingEnabled
to false
.
Disabling updates also sets the vehicle's status to OFFLINE
on Fleet Engine
backend.
Swift
vehicleReporter.locationTrackingEnabled = false
Objective-C
_vehicleReporter.locationTrackingEnabled = NO;