Skip to content
Open
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
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,33 @@ setOnArrival(null);
| `setOnReroutingRequestedByOffRoute` | `void` | Called when rerouting is triggered by off-route |
| `setOnTrafficUpdated` | `void` | Called when traffic data is updated |
| `setOnRemainingTimeOrDistanceChanged` | `void` | Called when remaining time or distance changes |
| `setOnTurnByTurn` | `{ navInfo: NavInfo }` | Called with turn-by-turn navigation info |
| `setOnTurnByTurn` | `TurnByTurnEvent[]` | Called with turn-by-turn navigation info |

Turn-by-turn events include the navigation state, distance and time estimates, the current step,
and remaining steps. Distances are in meters and durations are in seconds. Step fields that the
Navigation SDK does not provide are omitted. In particular, `roundaboutTurnNumber` is present only
for roundabout steps; check that optional fields are defined before using them.

```tsx
import { useEffect } from 'react';
import {
Maneuver,
useNavigation,
} from '@googlemaps/react-native-navigation-sdk';

const { setOnTurnByTurn } = useNavigation();

useEffect(() => {
setOnTurnByTurn(events => {
const step = events[0]?.currentStep;
if (step?.maneuver === Maneuver.TURN_LEFT) {
console.log(step.instruction ?? 'Turn left');
}
});

return () => setOnTurnByTurn(null);
}, [setOnTurnByTurn]);
```

### MapViewAutoController (useNavigationAuto hook)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,17 +156,56 @@ public static WritableMap getMapFromWaypoint(Waypoint waypoint) {
return map;
}

/**
* Translates a step into the payload of the turn-by-turn event. Every getter used here except the
* maneuver and the driving side is nullable, and a value the Navigation SDK does not provide is
* left out of the payload so that it reads as undefined on the JavaScript side.
*/
public static WritableMap getMapFromStepInfo(StepInfo stepInfo) {
WritableMap map = Arguments.createMap();
map.putInt("distanceFromPrevStepMeters", stepInfo.getDistanceFromPrevStepMeters());
map.putInt("timeFromPrevStepSeconds", stepInfo.getTimeFromPrevStepSeconds());
map.putInt("drivingSide", stepInfo.getDrivingSide());
map.putInt("stepNumber", stepInfo.getStepNumber());
map.putInt("maneuver", stepInfo.getManeuver());
map.putInt("roundaboutTurnNumber", stepInfo.getRoundaboutTurnNumber());
map.putString("exitNumber", stepInfo.getExitNumber());
map.putString("fullRoadName", stepInfo.getFullRoadName());
map.putString("instruction", stepInfo.getFullInstructionText());
map.putInt("drivingSide", stepInfo.getDrivingSide());

Integer distanceFromPrevStepMeters = stepInfo.getDistanceFromPrevStepMeters();
if (distanceFromPrevStepMeters != null) {
map.putInt("distanceFromPrevStepMeters", distanceFromPrevStepMeters);
}

Integer timeFromPrevStepSeconds = stepInfo.getTimeFromPrevStepSeconds();
if (timeFromPrevStepSeconds != null) {
map.putInt("timeFromPrevStepSeconds", timeFromPrevStepSeconds);
}

Integer stepNumber = stepInfo.getStepNumber();
if (stepNumber != null) {
map.putInt("stepNumber", stepNumber);
}

Integer roundaboutTurnNumber = stepInfo.getRoundaboutTurnNumber();
if (roundaboutTurnNumber != null && roundaboutTurnNumber >= 0) {
map.putInt("roundaboutTurnNumber", roundaboutTurnNumber);
}

String exitNumber = stepInfo.getExitNumber();
if (exitNumber != null) {
map.putString("exitNumber", exitNumber);
}

String fullRoadName = stepInfo.getFullRoadName();
if (fullRoadName != null) {
map.putString("fullRoadName", fullRoadName);
}

String simpleRoadName = stepInfo.getSimpleRoadName();
if (simpleRoadName != null) {
map.putString("simpleRoadName", simpleRoadName);
}

String instruction = stepInfo.getFullInstructionText();
if (instruction != null) {
map.putString("instruction", instruction);
}

return map;
}

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 @@ -106,4 +106,12 @@ describe('Navigation tests', () => {
await expectNoErrors();
await expectSuccess();
});

it('NT11 - test turn-by-turn events carry the declared step fields', async () => {
await selectTestByName('testTurnByTurnEventPayload');
await agreeToTermsAndConditions();
await waitForTestToFinish();
await expectNoErrors();
await expectSuccess();
});
});
11 changes: 11 additions & 0 deletions example/src/screens/IntegrationTestsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
testMinMaxZoomLevels,
testSetFollowingPerspective,
testNavInfoEventsAfterCleanup,
testTurnByTurnEventPayload,
NO_ERRORS_DETECTED_LABEL,
} from './integration_tests/integration_test';

Expand Down Expand Up @@ -327,6 +328,9 @@ const IntegrationTestsScreen = () => {
case 'testNavInfoEventsAfterCleanup':
await testNavInfoEventsAfterCleanup(getTestTools());
break;
case 'testTurnByTurnEventPayload':
await testTurnByTurnEventPayload(getTestTools());
break;
default:
resetTestState();
break;
Expand Down Expand Up @@ -563,6 +567,13 @@ const IntegrationTestsScreen = () => {
}}
testID="testNavInfoEventsAfterCleanup"
/>
<ExampleAppButton
title="testTurnByTurnEventPayload"
onPress={() => {
runTest('testTurnByTurnEventPayload');
}}
testID="testTurnByTurnEventPayload"
/>
</OverlayModal>
</View>
);
Expand Down
10 changes: 8 additions & 2 deletions example/src/screens/NavigationScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
NavigationView,
MapColorScheme,
CameraPerspective,
Maneuver,
NavigationNightMode,
NavigationSessionStatus,
type ArrivalEvent,
Expand Down Expand Up @@ -177,8 +178,13 @@ const NavigationScreen = () => {
setOnTrafficUpdated(() => showSnackbar('Traffic Updated'));
setOnStartGuidance(() => showSnackbar('Start Guidance'));

setOnTurnByTurn((_turnByTurn: TurnByTurnEvent[]) => {
console.debug(`Received ${_turnByTurn.length} turn-by-turn events`);
setOnTurnByTurn((turnByTurn: TurnByTurnEvent[]) => {
const currentStep = turnByTurn[0]?.currentStep;
console.debug(
`Received ${turnByTurn.length} turn-by-turn events, current step: ` +
`${currentStep ? Maneuver[currentStep.maneuver] : 'none'} ` +
`"${currentStep?.instruction ?? ''}"`
);
});
}, [
setOnNavigationReady,
Expand Down
148 changes: 148 additions & 0 deletions example/src/screens/integration_tests/integration_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import {
AudioGuidanceMode,
CameraPerspective,
DrivingSide,
Maneuver,
NavState,
TravelMode,
NavigationSessionStatus,
RouteStatus,
Expand Down Expand Up @@ -1976,3 +1979,148 @@ export const testNavInfoEventsAfterCleanup = async (testTools: TestTools) => {

await initializeNavigation(navigationController, failTest);
};

/**
* Verifies that a turn-by-turn event carries the fields that TurnByTurnEvent
* and StepInfo declare, so that the TypeScript types and the native emitters
* keep describing the same payload.
*/
export const testTurnByTurnEventPayload = async (testTools: TestTools) => {
const {
navigationController,
setOnNavigationReady,
setOnLocationChanged,
setOnTurnByTurn,
passTest,
failTest,
} = testTools;

// Accept ToS first
if (!(await acceptToS(navigationController, failTest))) {
return;
}

const startLocation: LatLng = {
lat: 37.79136614772824,
lng: -122.41565900473043,
};

setOnTurnByTurn(async (events: TurnByTurnEvent[]) => {
const event = events[0];
const step = event?.currentStep;
if (!event || !step) {
return;
}

setOnTurnByTurn(null);
await navigationController.cleanup();

if (NavState[event.navState] === undefined) {
return failTest(`navState is not a NavState value: ${event.navState}`);
}
if (typeof event.routeChanged !== 'boolean') {
return failTest(`routeChanged is not a boolean: ${event.routeChanged}`);
}
if (!Array.isArray(event.getRemainingSteps)) {
return failTest('getRemainingSteps is not an array');
}
if (Maneuver[step.maneuver] === undefined) {
return failTest(
`currentStep.maneuver is not a Maneuver value: ${step.maneuver}`
);
}
if (DrivingSide[step.drivingSide] === undefined) {
return failTest(
`currentStep.drivingSide is not a DrivingSide value: ${step.drivingSide}`
);
}
if (typeof step.stepNumber !== 'number') {
return failTest(
`currentStep.stepNumber is not a number: ${step.stepNumber}`
);
}
if (typeof step.distanceFromPrevStepMeters !== 'number') {
return failTest(
`currentStep.distanceFromPrevStepMeters is not a number: ${step.distanceFromPrevStepMeters}`
);
}
if (typeof step.timeFromPrevStepSeconds !== 'number') {
return failTest(
`currentStep.timeFromPrevStepSeconds is not a number: ${step.timeFromPrevStepSeconds}`
);
}
if (typeof step.instruction !== 'string') {
return failTest(
`currentStep.instruction is not a string: ${step.instruction}`
);
}
if (
step.fullRoadName !== undefined &&
typeof step.fullRoadName !== 'string'
) {
return failTest(
`currentStep.fullRoadName is not a string: ${step.fullRoadName}`
);
}
if (
step.simpleRoadName !== undefined &&
typeof step.simpleRoadName !== 'string'
) {
return failTest(
`currentStep.simpleRoadName is not a string: ${step.simpleRoadName}`
);
}
if (step.exitNumber !== undefined && typeof step.exitNumber !== 'string') {
return failTest(
`currentStep.exitNumber is not a string: ${step.exitNumber}`
);
}
if (
step.roundaboutTurnNumber !== undefined &&
typeof step.roundaboutTurnNumber !== 'number'
) {
return failTest(
`currentStep.roundaboutTurnNumber is not a number: ${step.roundaboutTurnNumber}`
);
}
if (
step.roundaboutTurnNumber !== undefined &&
step.roundaboutTurnNumber < 0
) {
return failTest(
`currentStep.roundaboutTurnNumber must be omitted for non-roundabouts: ${step.roundaboutTurnNumber}`
);
}

passTest();
});

setOnNavigationReady(async () => {
disableVoiceGuidanceForTests(navigationController);
navigationController.setTurnByTurnLoggingEnabled(true);

const located = await simulateAndWaitForLocation(
navigationController,
setOnLocationChanged,
startLocation
);
if (!located) {
return failTest(
'Timed out waiting for simulated location to be confirmed'
);
}
await navigationController.setDestination({
title: 'Grace Cathedral',
position: {
lat: 37.791957,
lng: -122.412529,
},
});
await navigationController.startGuidance();
await navigationController.simulator.simulateLocationsAlongExistingRoute({
speedMultiplier: 5,
});
});

await initializeNavigation(navigationController, failTest);
};
Loading
Loading