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
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ try {
showTrafficLights: true,
};

await navigationController.setDestinations([waypoint], routingOptions, displayOptions);
await navigationController.setDestinations([waypoint], { routingOptions, displayOptions });
await navigationController.startGuidance();
} catch (error) {
console.error('Error starting navigation', error);
Expand All @@ -240,6 +240,33 @@ try {
>
> To avoid this, ensure that the SDK has provided a valid user location before calling the setDestinations function. You can do this by subscribing to the onLocationChanged navigation callback and waiting for the first valid location update.

#### Using Route Tokens

You can use a pre-computed route from the [Routes API](https://developers.google.com/maps/documentation/routes) by providing a route token. This is useful when you want to ensure the navigation follows a specific route that was calculated server-side.

To use a route token:

1. Pass the token using `routeTokenOptions` instead of `routingOptions`
2. **Important:** The waypoints passed to `setDestinations` must match the waypoints used when generating the route token

```tsx
const waypoint = {
title: 'Destination',
position: { lat: 37.7749, lng: -122.4194 },
};

const routeTokenOptions = {
routeToken: 'your-route-token-from-routes-api',
travelMode: TravelMode.DRIVING, // Must match the travel mode used to generate the token
};

await navigationController.setDestinations([waypoint], { routeTokenOptions });
await navigationController.startGuidance();
```

> [!IMPORTANT]
> `routingOptions` and `routeTokenOptions` are mutually exclusive. Providing both will throw an error.


#### Adding navigation listeners

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.android.libraries.navigation.AlternateRoutesStrategy;
import com.google.android.libraries.navigation.ForceNightMode;
import com.google.android.libraries.navigation.Navigator;
import com.google.android.libraries.navigation.RoutingOptions;

public class EnumTranslationUtil {
public static AlternateRoutesStrategy getAlternateRoutesStrategyFromJsValue(int jsValue) {
Expand Down Expand Up @@ -108,4 +109,19 @@ public static CustomTypes.MapViewType getMapViewTypeFromJsValue(int jsValue) {
return MapColorScheme.FOLLOW_SYSTEM;
}
}

public static @RoutingOptions.TravelMode int getTravelModeFromJsValue(int jsValue) {
switch (jsValue) {
case 1:
return RoutingOptions.TravelMode.CYCLING;
case 2:
return RoutingOptions.TravelMode.WALKING;
case 3:
return RoutingOptions.TravelMode.TWO_WHEELER;
case 4:
return RoutingOptions.TravelMode.TAXI;
default:
return RoutingOptions.TravelMode.DRIVING;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@
import com.google.android.libraries.mapsplatform.turnbyturn.model.NavInfo;
import com.google.android.libraries.mapsplatform.turnbyturn.model.StepInfo;
import com.google.android.libraries.navigation.ArrivalEvent;
import com.google.android.libraries.navigation.CustomRoutesOptions;
import com.google.android.libraries.navigation.DisplayOptions;
import com.google.android.libraries.navigation.ListenableResultFuture;
import com.google.android.libraries.navigation.NavigationApi;
import com.google.android.libraries.navigation.NavigationApi.OnTermsResponseListener;
import com.google.android.libraries.navigation.Navigator;
import com.google.android.libraries.navigation.RoadSnappedLocationProvider;
import com.google.android.libraries.navigation.RoadSnappedLocationProvider.LocationListener;
import com.google.android.libraries.navigation.RouteSegment;
import com.google.android.libraries.navigation.RoutingOptions;
import com.google.android.libraries.navigation.SimulationOptions;
import com.google.android.libraries.navigation.SpeedAlertOptions;
import com.google.android.libraries.navigation.SpeedAlertSeverity;
Expand Down Expand Up @@ -429,22 +432,12 @@ private void createWaypoint(Map map) {
}
}

@ReactMethod
public void setDestination(
ReadableMap waypoint,
@Nullable ReadableMap routingOptions,
@Nullable ReadableMap displayOptions,
final Promise promise) {
WritableArray array = new WritableNativeArray();
array.pushMap(waypoint);
setDestinations(array, routingOptions, displayOptions, promise);
}

@ReactMethod
public void setDestinations(
ReadableArray waypoints,
@Nullable ReadableMap routingOptions,
@Nullable ReadableMap displayOptions,
@Nullable ReadableMap routeTokenOptions,
final Promise promise) {
if (!ensureNavigatorAvailable(promise)) {
return;
Expand All @@ -459,19 +452,44 @@ public void setDestinations(
createWaypoint(map);
}

if (routingOptions != null) {
if (displayOptions != null) {
// Get display options if provided
DisplayOptions parsedDisplayOptions =
displayOptions != null
? ObjectTranslationUtil.getDisplayOptionsFromMap(displayOptions.toHashMap())
: null;

// If route token options are provided, use CustomRoutesOptions
if (routeTokenOptions != null) {
CustomRoutesOptions customRoutesOptions;
try {
customRoutesOptions =
ObjectTranslationUtil.getCustomRoutesOptionsFromMap(routeTokenOptions.toHashMap());
} catch (IllegalStateException e) {
promise.reject("routeTokenMalformed", "The route token passed is malformed", e);
return;
}

if (parsedDisplayOptions != null) {
pendingRoute =
mNavigator.setDestinations(
mWaypoints,
ObjectTranslationUtil.getRoutingOptionsFromMap(routingOptions.toHashMap()),
ObjectTranslationUtil.getDisplayOptionsFromMap(displayOptions.toHashMap()));
mNavigator.setDestinations(mWaypoints, customRoutesOptions, parsedDisplayOptions);
} else {
pendingRoute = mNavigator.setDestinations(mWaypoints, customRoutesOptions);
}
} else if (routingOptions != null) {
RoutingOptions parsedRoutingOptions =
ObjectTranslationUtil.getRoutingOptionsFromMap(routingOptions.toHashMap());

if (parsedDisplayOptions != null) {
pendingRoute =
mNavigator.setDestinations(
mWaypoints,
ObjectTranslationUtil.getRoutingOptionsFromMap(routingOptions.toHashMap()));
mNavigator.setDestinations(mWaypoints, parsedRoutingOptions, parsedDisplayOptions);
} else {
pendingRoute = mNavigator.setDestinations(mWaypoints, parsedRoutingOptions);
}
} else if (parsedDisplayOptions != null) {
// No routing options provided: use defaults, but still honor display options if
// supplied.
pendingRoute =
mNavigator.setDestinations(mWaypoints, new RoutingOptions(), parsedDisplayOptions);
} else {
pendingRoute = mNavigator.setDestinations(mWaypoints);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.google.android.gms.maps.model.Polyline;
import com.google.android.libraries.mapsplatform.turnbyturn.model.StepInfo;
import com.google.android.libraries.navigation.AlternateRoutesStrategy;
import com.google.android.libraries.navigation.CustomRoutesOptions;
import com.google.android.libraries.navigation.DisplayOptions;
import com.google.android.libraries.navigation.NavigationRoadStretchRenderingData;
import com.google.android.libraries.navigation.RouteSegment;
Expand Down Expand Up @@ -146,8 +147,9 @@ public static RoutingOptions getRoutingOptionsFromMap(Map map) {
}

if (map.containsKey("travelMode")) {
options.travelMode(
CollectionUtil.getInt("travelMode", map, RoutingOptions.TravelMode.DRIVING));
int travelModeJsValue =
CollectionUtil.getInt("travelMode", map, RoutingOptions.TravelMode.DRIVING);
options.travelMode(EnumTranslationUtil.getTravelModeFromJsValue(travelModeJsValue));
}

if (map.containsKey("routingStrategy")) {
Expand All @@ -168,6 +170,21 @@ public static RoutingOptions getRoutingOptionsFromMap(Map map) {
return options;
}

public static CustomRoutesOptions getCustomRoutesOptionsFromMap(Map map)
throws IllegalStateException {
String routeToken = CollectionUtil.getString("routeToken", map);

CustomRoutesOptions.Builder builder = CustomRoutesOptions.builder().setRouteToken(routeToken);

if (map.containsKey("travelMode")) {
int travelModeJsValue =
CollectionUtil.getInt("travelMode", map, RoutingOptions.TravelMode.DRIVING);
builder.setTravelMode(EnumTranslationUtil.getTravelModeFromJsValue(travelModeJsValue));
}

return builder.build();
}

public static LatLng getLatLngFromMap(Map map) {
if (map.get(Constants.LAT_FIELD_KEY) == null || map.get(Constants.LNG_FIELD_KEY) == null) {
return null;
Expand Down
8 changes: 8 additions & 0 deletions example/e2e/navigation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,12 @@ describe('Navigation tests', () => {
await expectNoErrors();
await expectSuccess();
});

it('T08 - setDestinations with both routingOptions and routeTokenOptions should throw error', async () => {
await selectTestByName('testRouteTokenOptionsValidation');
await agreeToTermsAndConditions();
await waitForTestToFinish();
await expectNoErrors();
await expectSuccess();
});
});
8 changes: 8 additions & 0 deletions example/e2e/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ export const initializeIntegrationTestsPage = async () => {
};

export const selectTestByName = async name => {
await waitFor(element(by.id('tests_menu_button')))
.toBeVisible()
.withTimeout(10000);
await element(by.id('tests_menu_button')).tap();
// Scroll to make the test button visible before tapping
await waitFor(element(by.id(name)))
.toBeVisible()
.whileElement(by.id('overlay_scroll_view'))
.scroll(100, 'down');
await element(by.id(name)).tap();
};
8 changes: 4 additions & 4 deletions example/ios/SampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -641,12 +641,12 @@
CODE_SIGN_ENTITLEMENTS = SampleApp/SampleApp.entitlements;
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "SampleApp/Info-CarPlay.plist";
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"CARPLAY=1",
);
INFOPLIST_FILE = "SampleApp/Info-CarPlay.plist";
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -674,12 +674,12 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = SampleApp/SampleApp.entitlements;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = "SampleApp/Info-CarPlay.plist";
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"CARPLAY=1",
);
INFOPLIST_FILE = "SampleApp/Info-CarPlay.plist";
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@react-native/typescript-config": "0.81.1",
"@types/jest": "^29.5.14",
"@types/node": "^22.9.0",
"detox": "^20.27.6",
"detox": "^20.46.3",
"jest": "^29.7.0",
"react-native-builder-bob": "^0.40.13",
"react-native-monorepo-config": "^0.1.9",
Expand Down
13 changes: 12 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ExampleAppButton } from './controls/ExampleAppButton';
import NavigationScreen from './screens/NavigationScreen';
import MultipleMapsScreen from './screens/MultipleMapsScreen';
import MapIdScreen from './screens/MapIdScreen';
import RouteTokenScreen from './screens/RouteTokenScreen';
import {
NavigationProvider,
TaskRemovedBehavior,
Expand All @@ -41,6 +42,7 @@ export type ScreenNames = [
'Navigation',
'Multiple maps',
'Map ID',
'Route Token',
'Integration tests',
];

Expand Down Expand Up @@ -70,7 +72,9 @@ const HomeScreen = () => {
}, [navigationController]);

return (
<View style={[CommonStyles.centered, { paddingBottom: insets.bottom }]}>
<View
style={[CommonStyles.centered, { paddingBottom: insets.bottom + 100 }]}
>
{/* SDK Version Display */}
<View style={{ padding: 16, alignItems: 'center' }}>
<Text style={{ fontSize: 16, fontWeight: 'bold', color: '#333' }}>
Expand All @@ -95,6 +99,12 @@ const HomeScreen = () => {
onPress={() => isFocused && navigate('Map ID')}
/>
</View>
<View style={CommonStyles.buttonContainer}>
<ExampleAppButton
title="Route Token"
onPress={() => isFocused && navigate('Route Token')}
/>
</View>
<View style={CommonStyles.container} />
<View style={CommonStyles.buttonContainer}>
<ExampleAppButton
Expand Down Expand Up @@ -131,6 +141,7 @@ export default function App() {
<Stack.Screen name="Navigation" component={NavigationScreen} />
<Stack.Screen name="Multiple maps" component={MultipleMapsScreen} />
<Stack.Screen name="Map ID" component={MapIdScreen} />
<Stack.Screen name="Route Token" component={RouteTokenScreen} />
<Stack.Screen
name="Integration tests"
component={IntegrationTestsScreen}
Expand Down
14 changes: 6 additions & 8 deletions example/src/controls/navigationControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,10 @@ const NavigationControls = ({
showTrafficLights: true,
};

navigationController.setDestination(
waypoint,
navigationController.setDestination(waypoint, {
routingOptions,
displayOptions
);
displayOptions,
});
};

const setLocationFromCameraLocation = async () => {
Expand Down Expand Up @@ -166,11 +165,10 @@ const NavigationControls = ({
showTrafficLights: true,
};

navigationController.setDestinations(
waypoints,
navigationController.setDestinations(waypoints, {
routingOptions,
displayOptions
);
displayOptions,
});
};

const setFollowingPerspective = (index: CameraPerspective) => {
Expand Down
1 change: 1 addition & 0 deletions example/src/helpers/overlayModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const OverlayModal: React.FC<OverlayModalProps> = ({
<Pressable style={styles.overlayTouchable} onPress={closeOverlay} />
<View style={modalContentStyle}>
<ScrollView
testID="overlay_scroll_view"
showsVerticalScrollIndicator={true}
persistentScrollbar={true}
style={styles.scrollContainer}
Expand Down
Loading
Loading