Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,14 @@ module.exports = {
'@typescript-eslint/no-require-imports': 'off',
},
},
// Test files
// Test files and Jest setup
{
files: ['**/*.test.js', '**/*.test.ts', '**/e2e/**/*.js'],
files: [
'**/*.test.js',
'**/*.test.ts',
'**/e2e/**/*.js',
'jest.setup.js',
],
env: {
jest: true,
},
Expand Down
119 changes: 112 additions & 7 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ Version 0.5.0 introduces React Native's **New Architecture** (TurboModules) as a

### Summary of Breaking Changes

| Category | Change |
| -------------- | -------------------------------------------------------------------------------------- |
| Architecture | New Architecture required (React Native 0.79+) |
| Navigation SDK | Upgraded to `@googlemaps/react-native-navigation-sdk` 0.15.x (New Architecture) |
| Native Modules | Migrated from `NativeModules` bridge to TurboModules (JSI) |
| React Native | Support for React Native versions below 0.79.x has been dropped |
| Category | Change |
| -------------- | ----------------------------------------------------------------------------------------------- |
| Architecture | New Architecture required (React Native 0.79+) |
| Navigation SDK | Upgraded to `@googlemaps/react-native-navigation-sdk` 0.15.x (New Architecture) |
| Native Modules | Migrated from `NativeModules` bridge to TurboModules (JSI) |
| React Native | Support for React Native versions below 0.79.x has been dropped |
| Auth Tokens | `onGetToken` callback is now called on-demand by the native SDK for each token request |
| Events | Vehicle reporter events and status updates use TurboModule EventEmitter pattern |
| Types | `VehicleStop.waypoint` is now optional |
| Types | `OnStatusUpdateCallback` now uses `DriverStatusLevel` and `DriverStatusCode` enums |
| Types | `VehicleReporterListener` removed; use `setOnVehicleUpdateSucceed` / `setOnVehicleUpdateFailed` |

### Prerequisites

Expand Down Expand Up @@ -106,7 +111,41 @@ import {
> [!NOTE]
> The platform-specific module name difference for Ridesharing (`RideSharingModule` on iOS vs `RidesharingModule` on Android) has been unified under the TurboModule system.

### Step 4: Update Build Configuration
### Step 4: Update Auth Token Handling (Breaking Change)

The auth token mechanism has been completely redesigned. Previously, the `onGetToken` callback was only called once during initialization and the token would go stale. Now, the native Driver SDK calls `onGetToken` **on demand** whenever it needs a fresh token (e.g., on each location update). This matches the [recommended pattern from Google Maps documentation](https://developers.google.com/maps/documentation/transportation-platform/driver-sdk) and the Flutter Driver SDK implementation.

**Your `onGetToken` callback must now fetch a fresh token on every call.**

#### Before (0.4.x)

```tsx
// ❌ Token was fetched once and stored in state — went stale
const [authToken, setAuthToken] = useState<string | null>(null);

useEffect(() => {
fetchTokenFromBackend().then(setAuthToken);
}, []);

await driverApi.initialize(providerId, vehicleId, () => {
return Promise.resolve(authToken || '');
}, onStatusUpdate);
```

#### After (0.5.x)

```tsx
// ✅ Fresh token fetched on every native SDK request
await driverApi.initialize(providerId, vehicleId, async (tokenContext) => {
const response = await fetch(`${BASE_URL}/token/driver/${tokenContext.vehicleId}`);
const { token } = await response.json();
return token;
}, onStatusUpdate);
```

The `tokenContext` parameter provides `vehicleId` and `taskId` from the native SDK's authorization context, which you can use when requesting tokens from your backend.

### Step 5: Update Build Configuration

#### Android

Expand All @@ -126,6 +165,72 @@ Ensure your `Podfile` specifies iOS 16.0+ as the deployment target:
platform :ios, '16.0'
```

### Step 6: VehicleStop.waypoint Is Now Optional

The `waypoint` field on `VehicleStop` is now optional (`waypoint?: Waypoint`) to match cases where the native SDK returns a stop without waypoint data. If your code accesses `stop.waypoint`, add a null check:

```diff
- const position = stop.waypoint.position;
+ const position = stop.waypoint?.position;
```

### Step 7: Update OnStatusUpdateCallback Usage

The `onStatusUpdate` callback passed to `initialize` now uses typed enums instead of raw strings:

```diff
- (statusLevel: string, statusCode: string, statusMsg: string) => {
+ (statusLevel: DriverStatusLevel, statusCode: DriverStatusCode, statusMsg: string) => {
console.log(statusLevel, statusCode, statusMsg);
}
```

Import the enums if you reference them directly:

```typescript
import { DriverStatusLevel, DriverStatusCode } from '@googlemaps/react-native-driver-sdk';
```

> [!NOTE]
> **Platform availability:** `onStatusUpdate` fires on **Android only**. On iOS, use the vehicle reporter's `setOnVehicleUpdateSucceed` and `setOnVehicleUpdateFailed` methods to receive vehicle update callbacks.

### Step 8: Replace VehicleReporterListener with Individual Setters

The `VehicleReporterListener` interface and `setListener()` method on the vehicle reporter have been removed. Use the individual setter methods instead:

#### Before (0.4.x)

```tsx
reporter.setListener({
onVehicleUpdateSucceed(vehicleUpdate) {
console.log('onVehicleUpdateSucceed: ', vehicleUpdate);
},
onVehicleUpdateFailed(_vehicleUpdate, error) {
console.log('onVehicleUpdateFailed: ', error);
},
});
```

#### After (0.5.x)

```tsx
reporter.setOnVehicleUpdateSucceed(vehicleUpdate => {
console.log('onVehicleUpdateSucceed: ', vehicleUpdate);
});
reporter.setOnVehicleUpdateFailed((vehicleUpdate, error) => {
console.log(
'onVehicleUpdateFailed: ',
error.code,
error.message,
'vehicleState:',
vehicleUpdate.vehicleState
);
});
```

> [!NOTE]
> **Platform availability:** Vehicle reporter update callbacks fire on **iOS only**. On Android, use `onStatusUpdate` (passed to `initialize`) instead.

### Need Help?

If you encounter issues during migration:
Expand Down
62 changes: 33 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,25 +175,25 @@ To set up, specify your API key in the application delegate `ios/Runner/AppDeleg
3. Second step is to initialize the Api. Navigation must be initialized before the Driver SDK is initialized.

```typescript
await ridesharingDriverApi
.initialize(
PROVIDER_ID,
VEHICLE_ID,
(tokenContext) => {
// Check if the token is expired, in such case request a new one.
return Promise.resolve(authToken || "");
},
(statusLevel, statusCode, message) => {
console.log("onStatusUpdate: " + statusLevel + " " + statusCode + " " + message);
}
);
await ridesharingDriverApi.initialize(
PROVIDER_ID,
VEHICLE_ID,
async (tokenContext) => {
// Fetch a fresh token from your backend.
// tokenContext.vehicleId contains the vehicle ID that needs the token.
const token = await fetchTokenFromBackend(tokenContext.vehicleId);
return token;
},
// Android only — on iOS, use vehicleReporter.setOnVehicleUpdateSucceed/setOnVehicleUpdateFailed instead.
(statusLevel, statusCode, message) => {
console.log("onStatusUpdate: " + statusLevel + " " + statusCode + " " + message);
}
);
```

Note: The `initialize` method takes a `onGetTokenCallback` field as parameter. This will be called periodically to ensure the token stays refresh while there's requests to Fleet Engine. Please make sure to check that the token is valid (e.g. checking expiration time) before setting it.
Note: The `initialize` method takes an `onGetToken` callback as parameter. This callback is invoked by the native SDK whenever a fresh auth token is needed (e.g. on each location update cycle). The callback receives an `AuthTokenContext` with the `vehicleId` and should return a `Promise<string>` that resolves to a valid token. Always fetch a fresh token in this callback rather than caching one.


#### Getting a `RidesharingVehicleReporter`

The vehicle reporter allows developers to enable/disable location reporting to Fleet Engine, as well as to report changes in the vehicle state (E.g. Online or Offline).

```typescript
Expand All @@ -218,21 +218,24 @@ The vehicle reporter allows developers to enable/disable location reporting to F
2. Second step is to initialize the Api.

```typescript
await deliveryApi
.initialize(
PROVIDER_ID,
DELIVERY_VEHICLE_ID,
(tokenContext) => {
// Check if the token is expired, in such case request a new one.
return Promise.resolve(authToken || "");
},
(statusLevel, statusCode, message) => {
console.log("onStatusUpdate: " + statusLevel + " " + statusCode + " " + message);
}
);
await deliveryApi.initialize(
PROVIDER_ID,
DELIVERY_VEHICLE_ID,
async (tokenContext) => {
// Fetch a fresh token from your backend.
// tokenContext.vehicleId contains the vehicle ID that needs the token.
// tokenContext.taskId may contain the task ID for delivery-specific tokens.
const token = await fetchTokenFromBackend(tokenContext.vehicleId);
return token;
},
// Android only — on iOS, use vehicleReporter.setOnVehicleUpdateSucceed/setOnVehicleUpdateFailed instead.
(statusLevel, statusCode, message) => {
console.log("onStatusUpdate: " + statusLevel + " " + statusCode + " " + message);
}
);
```

Note: The `initialize` method takes a `onGetTokenCallback` field as parameter. This will be called periodically to ensure the token stays refresh while there's requests to Fleet Engine. Please make sure to check that the token is valid (e.g. checking expiration time) before setting it.
Note: The `initialize` method takes an `onGetToken` callback as parameter. This callback is invoked by the native SDK whenever a fresh auth token is needed (e.g. on each location update cycle). The callback receives an `AuthTokenContext` with the `vehicleId` (and optionally `taskId` for delivery) and should return a `Promise<string>` that resolves to a valid token. Always fetch a fresh token in this callback rather than caching one.


#### Getting a `DeliveryVehicleReporter`
Expand All @@ -246,11 +249,12 @@ The vehicle reporter allows developers to enable/disable location reporting to F

#### Getting a `DeliveryVehicleManager`

The vehicle managers allows developers to fetch the `DeliveryVehicle` linked to the Driver Api from Fleet Engine.
The vehicle manager allows developers to fetch the `DeliveryVehicle` linked to the Driver Api from Fleet Engine.

```typescript
const vehicleManager = deliveryApi.getDeliveryVehicleManager()
const deliveryVehicle = await vehicleManager.getDeliveryVehicle();
console.log(deliveryVehicle.vehicleName, deliveryVehicle.vehicleId, deliveryVehicle.vehicleStops);
```

### Other APIs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
import static java.util.Objects.requireNonNull;

import android.app.Application;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.google.android.libraries.mapsplatform.transportation.driver.api.base.data.DriverContext;
import com.google.android.libraries.mapsplatform.transportation.driver.api.base.data.DriverContext.DriverStatusListener.StatusCode;
import com.google.android.libraries.mapsplatform.transportation.driver.api.base.data.DriverContext.DriverStatusListener.StatusLevel;
Expand All @@ -47,14 +45,26 @@ public class DeliveryDriverModule extends NativeDeliveryDriverModuleSpec {
public static final String REACT_CLASS = NAME;

private DeliveryVehicleReporter vehicleReporter = null;
private DriverAuthTokenFactory tokenFactory = new DriverAuthTokenFactory();
private final DriverAuthTokenFactory tokenFactory = new DriverAuthTokenFactory();

ReactApplicationContext reactContext;
private int listenerCount = 0;

public DeliveryDriverModule(ReactApplicationContext context) {
super(context);
this.reactContext = context;

// Wire up the token factory to emit events to JS when a token is needed.
tokenFactory.setTokenRequestCallback(
(requestId, vehicleId, taskId) -> {
UiThreadUtil.runOnUiThread(
() -> {
WritableMap map = Arguments.createMap();
map.putString("requestId", requestId);
map.putString("vehicleId", vehicleId);
map.putString("taskId", taskId);
emitOnGetToken(map);
});
});
}

@Override
Expand Down Expand Up @@ -91,7 +101,7 @@ public void createDeliveryDriverInstance(String providerId, String vehicleId, Pr
NavigationApi.getRoadSnappedLocationProvider(application))
.setDriverStatusListener(
(statusLevel, statusCode, statusMsg, error) -> {
updateStatus(statusLevel, statusCode, statusMsg);
emitStatusUpdate(statusLevel, statusCode, statusMsg);
})
.build();

Expand Down Expand Up @@ -157,14 +167,18 @@ public void getDriverSdkVersion(Promise promise) {
/** Clears the instance of the DeliveryDriverApi */
@Override
public void clearInstance(Promise promise) {
try {
vehicleReporter = null;
DeliveryDriverApi.clearInstance();

promise.resolve(true);
} catch (Exception e) {
promise.reject(e.toString(), e.getMessage(), e);
}
UiThreadUtil.runOnUiThread(
() -> {
try {
tokenFactory.cancelAllPendingRequests();
vehicleReporter = null;
DeliveryDriverApi.clearInstance();

promise.resolve(true);
} catch (Exception e) {
promise.reject(e.toString(), e.getMessage(), e);
}
});
}

/**
Expand Down Expand Up @@ -209,54 +223,25 @@ public void setAbnormalTerminationReporting(boolean isEnabled) {
DeliveryDriverApi.setAbnormalTerminationReportingEnabled(isEnabled);
}

private void showToast(String errorMessage) {
Log.d(TAG, "showToast: " + errorMessage);
/** Called from JS to resolve a pending auth token request. */
@Override
public void resolveAuthToken(String requestId, String token) {
tokenFactory.resolveToken(requestId, token);
}

/**
* The function that accepts update codes from the driverContext listener
*
* @param statusLevel
* @param statusCode
* @param statusMsg
*/
public void updateStatus(StatusLevel statusLevel, StatusCode statusCode, String statusMsg) {
/** Called from JS to reject a pending auth token request. */
@Override
public void rejectAuthToken(String requestId, String error) {
tokenFactory.rejectToken(requestId, error);
}

private void emitStatusUpdate(StatusLevel statusLevel, StatusCode statusCode, String statusMsg) {
if (reactContext != null) {
WritableMap map = Arguments.createMap();
map.putString("statusLevel", statusLevel.toString());
map.putString("statusCode", statusCode.toString());
map.putString("statusMsg", statusMsg);

this.reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("updateStatus", map);
}
}

/**
* Create an AuthTokenFactory to be used by DriverContext when creating a DeliveryDriverInstance.
*
* @param token jwt token from an authentication service
* @param vehicleId the vehicle ID
* @param promise
*/
@Override
public void setAuthToken(String token, String vehicleId, Promise promise) {
try {
tokenFactory.setToken(token);
promise.resolve(null);
} catch (Exception e) {
promise.reject(e.getMessage());
emitOnStatusUpdate(map);
}
}

@Override
public void addListener(String eventName) {
listenerCount++;
}

@Override
public void removeListeners(double count) {
listenerCount -= (int) count;
}
}
Loading
Loading