A React Native wrapper around the Google Navigation SDK for iOS and Android. Provides a native turn-by-turn navigation view with full Fabric (New Architecture) support and an optional Expo config plugin.
Key features:
- Turn-by-turn navigation with voice guidance
- Multi-stop routes with per-waypoint arrival events
- Route optimization via Google Routes API
- OTP-based delivery verification hook
- Background location streaming
- Simulation mode for testing without real GPS
- Theming, camera control, markers, and traffic overlays
- Expo config plugin for zero-config native setup
Important: The Google Navigation SDK requires a Google Maps Platform account with the Navigation SDK enabled. This is a paid API — you must have a valid billing account and API key with the Navigation SDK enabled for both iOS and Android.
- Prerequisites
- Installation
- Expo Setup
- Bare React Native Setup
- Basic Usage
- API Reference
- Hooks
- Route Optimization
- Enums
- Theming
- Simulation Mode
- Troubleshooting
- Contributing
- License
- React Native 0.76+ (New Architecture / Fabric required)
- iOS 16+ / Android API 24+
- A Google Cloud project with these APIs enabled:
- Navigation SDK for iOS
- Navigation SDK for Android
- Places API (if using place search in your app)
-
Routes API (if using
optimizeWaypointOrder)
- API keys for iOS and Android with the above APIs enabled
- Go to Google Cloud Console
- Create or select a project
- Enable the Navigation SDK for both iOS and Android
- Go to Credentials → Create Credentials → API Key
- Restrict the key:
- iOS key: restrict to iOS apps with your bundle identifier
- Android key: restrict to Android apps with your package name and SHA-1 fingerprint
npm install JMilanya/react-native-google-navOr with yarn:
yarn add JMilanya/react-native-google-navGitHub installs build from source. During install, npm/yarn/pnpm run this package’s prepare script (bob build && tsc) to generate lib/ and plugin/build/. That requires the package’s devDependencies (notably react-native-builder-bob and typescript) and install scripts must be allowed. If scripts are blocked (e.g., --ignore-scripts), the Expo plugin won’t load. Bun users must trust the package first (see below).
bun pm trust react-native-google-nav
bun add JMilanya/react-native-google-navnpm install react react-native
# If using Expo:
npm install expoThe library ships with an Expo config plugin that handles all native configuration automatically.
{
"expo": {
"plugins": [
[
"react-native-google-nav/expo-plugin",
{
"androidApiKey": "YOUR_ANDROID_API_KEY",
"iosApiKey": "YOUR_IOS_API_KEY"
}
]
],
"ios": {
"infoPlist": {
"NSLocationWhenInUseUsageDescription": "This app needs your location for navigation.",
"NSLocationAlwaysAndWhenInUseUsageDescription": "This app needs background location for navigation.",
"UIBackgroundModes": ["location"]
}
},
"android": {
"permissions": [
"ACCESS_FINE_LOCATION",
"ACCESS_COARSE_LOCATION"
]
}
}
}npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:androidNote: This library uses native code and cannot run in Expo Go. You must use a development build.
- Add the Google Navigation SDK to your
Podfile:
# ios/Podfile
pod 'GoogleNavigation', '~> 9.1'- Initialize the SDK in
AppDelegate.mm:
#import <GoogleMaps/GoogleMaps.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GMSServices provideAPIKey:@"YOUR_IOS_API_KEY"];
// ... rest of your setup
}- Add location permissions to
Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs your location for navigation.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs background location for navigation.</string>- Install pods:
cd ios && pod install- Add the Navigation SDK dependency to your app's
build.gradle:
// android/app/build.gradle
dependencies {
implementation "com.google.android.libraries.navigation:navigation:7.1.0"
}- Add your API key to
AndroidManifest.xml:
<application>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_ANDROID_API_KEY" />
</application>- Add location permissions to
AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />- Ensure
minSdkVersionis at least 24 inandroid/build.gradle.
import React, { useRef } from 'react';
import { View, Button } from 'react-native';
import {
GoogleNavProvider,
GoogleNavView,
TravelMode,
type GoogleNavViewRef,
} from 'react-native-google-nav';
export default function NavigationScreen() {
const navRef = useRef<GoogleNavViewRef>(null);
const startNavigation = () => {
navRef.current?.setDestinations(
[
{
position: { latitude: -1.2921, longitude: 36.8219 },
title: 'Nairobi CBD',
},
{
position: { latitude: -1.2864, longitude: 36.8172 },
title: 'University of Nairobi',
},
],
{ travelMode: TravelMode.DRIVING }
);
};
return (
<GoogleNavProvider apiKey="YOUR_API_KEY">
<View style={{ flex: 1 }}>
<GoogleNavView
ref={navRef}
style={{ flex: 1 }}
navigationUIEnabled
headerEnabled
tripProgressBarEnabled
speedometerEnabled
onNavigationReady={() => console.log('Navigation SDK ready')}
onRouteReady={(e) =>
console.log(`Route: ${e.totalDistanceMeters}m, ${e.totalTimeSeconds}s`)
}
onArrival={(e) =>
console.log(`Arrived at stop #${e.waypointIndex}: ${e.waypointTitle}`)
}
/>
<Button title="Start Navigation" onPress={startNavigation} />
<Button
title="Start Simulation"
onPress={() => navRef.current?.startSimulation()}
/>
</View>
</GoogleNavProvider>
);
}Wraps your navigation screen. Initializes the SDK and shows the Google Terms & Conditions dialog.
<GoogleNavProvider apiKey="YOUR_API_KEY">
{children}
</GoogleNavProvider>| Prop | Type | Required | Description |
|---|---|---|---|
apiKey |
string |
Yes | Your Google Maps API key |
children |
ReactNode |
Yes | Child components |
The native navigation map view. Must be inside a GoogleNavProvider.
Access these via a ref on GoogleNavView:
| Method | Signature | Description |
|---|---|---|
setDestinations |
(waypoints: Waypoint[], options?: RoutingOptions) => void |
Set one or more destinations and compute the route |
addDestination |
(waypoint: Waypoint, atIndex?: number) => void |
Add a waypoint to the current route |
removeDestination |
(atIndex: number) => void |
Remove a waypoint by index |
updateDestination |
(atIndex: number, waypoint: Waypoint) => void |
Update a waypoint's position or metadata |
startGuidance |
() => void |
Start real GPS turn-by-turn guidance |
stopGuidance |
() => void |
Stop guidance |
clearDestinations |
() => void |
Clear all destinations and route |
recenterCamera |
() => void |
Re-center camera on the navigation arrow |
showRouteOverview |
() => void |
Zoom out to show the full route |
moveCamera |
(position: CameraPosition) => void |
Move camera to a specific position |
setAudioGuidance |
(guidance: AudioGuidance) => void |
Set voice guidance level |
startSimulation |
() => void |
Start simulated navigation along the route |
stopSimulation |
() => void |
Stop simulation |
addMarker |
(marker: Marker) => void |
Add a map marker |
removeMarker |
(markerId: string) => void |
Remove a marker by ID |
clearMap |
() => void |
Remove all markers |
getCurrentRoute |
() => Promise<OnRoutePolylineEvent> |
Get the current route as an encoded polyline |
| Prop | Type | Default | Description |
|---|---|---|---|
style |
ViewStyle |
— | View style |
mapType |
MapType |
NORMAL |
Map type (normal, satellite, terrain, hybrid) |
mapColorScheme |
number |
0 |
Color scheme (0=system, 1=light, 2=dark) |
compassEnabled |
boolean |
true |
Show compass |
myLocationButtonEnabled |
boolean |
true |
Show "my location" button |
myLocationEnabled |
boolean |
true |
Show blue dot for current location |
rotateGesturesEnabled |
boolean |
true |
Allow rotate gestures |
scrollGesturesEnabled |
boolean |
true |
Allow scroll/pan gestures |
tiltGesturesEnabled |
boolean |
true |
Allow tilt gestures |
zoomGesturesEnabled |
boolean |
true |
Allow zoom gestures |
trafficEnabled |
boolean |
false |
Show traffic layer |
buildingsEnabled |
boolean |
true |
Show 3D buildings |
indoorEnabled |
boolean |
false |
Show indoor maps |
navigationUIEnabled |
boolean |
true |
Show navigation UI (turn arrows, etc.) |
headerEnabled |
boolean |
true |
Show turn-by-turn header |
footerEnabled |
boolean |
true |
Show navigation footer |
tripProgressBarEnabled |
boolean |
false |
Show trip progress bar |
speedometerEnabled |
boolean |
false |
Show speedometer |
speedLimitIconEnabled |
boolean |
false |
Show speed limit icon |
recenterButtonEnabled |
boolean |
true |
Show recenter button |
trafficIncidentCardsEnabled |
boolean |
true |
Show traffic incident cards |
followingPerspective |
CameraPerspective |
TILTED |
Camera perspective during navigation |
theme |
NavigationTheme |
— | Custom navigation theme colors |
| Event | Payload | Description |
|---|---|---|
onMapReady |
— | Map view is ready |
onMapClick |
{ latitude, longitude } |
User tapped the map |
onNavigationReady |
— | Navigation SDK initialized |
onRouteReady |
{ totalTimeSeconds, totalDistanceMeters } |
Route computed successfully |
onArrival |
{ waypointIndex, isFinalDestination, waypointTitle, waypointLatitude, waypointLongitude, waypointMetadata } |
Arrived at a waypoint |
onLocationChanged |
{ latitude, longitude, bearing, speed, accuracy } |
Location update (throttled to every 30s) |
onTurnByTurnUpdated |
{ distanceRemainingMeters, timeRemainingSeconds, fullRoadName, maneuver } |
Turn-by-turn instruction updated |
onNavigationStateChanged |
{ state } |
Navigation state changed |
onRemainingTimeOrDistanceChanged |
{ remainingTimeSeconds, remainingDistanceMeters } |
ETA/distance updated |
onRouteChanged |
— | Route was recalculated |
onTrafficUpdated |
— | Traffic data updated |
onRerouting |
— | Rerouting in progress |
onSpeeding |
{ percentageAboveLimit } |
Driver is speeding |
onWaypointETAsUpdated |
{ waypoints: WaypointETA[] } |
Per-waypoint ETA updates |
Access the navigation context from any child of GoogleNavProvider.
import { useGoogleNav } from 'react-native-google-nav';
function MyComponent() {
const { isInitialized, termsAccepted, navigationState } = useGoogleNav();
// ...
}Backend-agnostic OTP delivery verification hook. Generates a code, calls your backend to send it to the customer, and verifies the code the driver enters.
import { useOTPVerification } from 'react-native-google-nav';
function OTPScreen({ waypointIndex, orderId }) {
const otp = useOTPVerification({
waypointIndex,
waypointTitle: 'Stop 1',
expirySeconds: 300,
maxAttempts: 3,
callbacks: {
onGenerate: async (wpIndex, code, expiresAt) => {
// Send OTP to your backend → backend pushes to customer
await api.sendOTP({ orderId, code, expiresAt });
},
onVerify: async (wpIndex, code) => {
// Validate OTP with your backend
const result = await api.verifyOTP({ orderId, code });
return result.valid;
},
},
});
return (
<View>
{otp.status === 'idle' && (
<Button onPress={otp.generate} title="Generate OTP" />
)}
{otp.status === 'sent' && (
<>
<Text>Time remaining: {otp.remainingSeconds}s</Text>
<TextInput
value={otp.enteredCode}
onChangeText={otp.setEnteredCode}
maxLength={6}
keyboardType="number-pad"
/>
<Button onPress={otp.verify} title="Verify" />
</>
)}
{otp.status === 'verified' && <Text>Delivery confirmed!</Text>}
{otp.status === 'failed' && <Text>Verification failed</Text>}
</View>
);
}Return values:
| Property | Type | Description |
|---|---|---|
status |
OTPDeliveryStatus |
Current status: idle, generating, sent, verifying, verified, failed, expired
|
otp |
string |
The generated OTP code |
enteredCode |
string |
The code entered by the driver |
setEnteredCode |
(code: string) => void |
Update the entered code |
generate |
() => Promise<void> |
Generate a new OTP and call onGenerate
|
verify |
() => Promise<boolean> |
Verify the entered code via onVerify
|
reset |
() => void |
Reset to idle state |
attempts |
number |
Number of verification attempts |
maxAttempts |
number |
Maximum allowed attempts |
remainingSeconds |
number |
Seconds until OTP expires |
isExpired |
boolean |
Whether the OTP has expired |
record |
OTPDeliveryRecord | null |
Full delivery record |
Stream location updates in the background via a foreground service (Android) or background location mode (iOS).
import { useBackgroundLocation } from 'react-native-google-nav';
function TrackingComponent() {
useBackgroundLocation(
{
intervalMs: 30000,
notificationTitle: 'Delivery Active',
notificationText: 'Tracking your delivery route',
},
(location) => {
console.log(`Background: ${location.latitude}, ${location.longitude}`);
// Send to your backend
}
);
}Options:
| Option | Type | Default | Description |
|---|---|---|---|
intervalMs |
number |
5000 |
Update interval in ms (Android only) |
notificationTitle |
string |
'Location Active' |
Foreground service notification title (Android) |
notificationText |
string |
'Tracking delivery location' |
Foreground service notification text (Android) |
Optimize waypoint order for the most efficient route using the Google Routes API.
import { optimizeWaypointOrder, TravelMode } from 'react-native-google-nav';
const result = await optimizeWaypointOrder({
apiKey: 'YOUR_API_KEY',
origin: { latitude: -1.28, longitude: 36.82 },
waypoints: [
{ position: { latitude: -1.30, longitude: 36.80 }, title: 'Stop A' },
{ position: { latitude: -1.29, longitude: 36.85 }, title: 'Stop B' },
{ position: { latitude: -1.31, longitude: 36.83 }, title: 'Stop C' },
],
travelMode: TravelMode.DRIVING,
});
// result.waypoints — reordered waypoints
// result.optimizedOrder — original indices in optimized order
// result.totalDistanceMeters
// result.totalDurationSeconds
navRef.current?.setDestinations(result.waypoints, {
travelMode: TravelMode.DRIVING,
});Note: Route optimization requires the Routes API enabled on your API key. It supports DRIVING and TWO_WHEELER modes only, with a maximum of 25 intermediate waypoints.
| Value | Description |
|---|---|
DRIVING (0) |
Car/truck routing |
CYCLING (1) |
Bicycle routing |
WALKING (2) |
Pedestrian routing |
TWO_WHEELER (3) |
Motorbike/scooter routing |
| Value | Description |
|---|---|
SILENT (0) |
No audio |
VOICE_ALERTS_ONLY (1) |
Alert sounds only |
VOICE_ALERTS_AND_GUIDANCE (2) |
Full voice guidance |
VIBRATION (3) |
Vibration alerts |
| Value | Description |
|---|---|
TILTED (0) |
3D tilted perspective (default for driving) |
TOP_DOWN_NORTH (1) |
Top-down, north-up |
TOP_DOWN_HEADING (2) |
Top-down, heading-up |
| Value | Description |
|---|---|
NORMAL (1) |
Standard map |
SATELLITE (2) |
Satellite imagery |
TERRAIN (3) |
Terrain map |
HYBRID (4) |
Satellite + labels |
Customize navigation UI colors:
<GoogleNavView
theme={{
headerBackgroundColor: '#1a73e8',
headerSecondaryBackgroundColor: '#1565c0',
headerTextColor: '#ffffff',
headerManeuverIconColor: '#ffffff',
routeColor: '#4285f4',
routeTraveledColor: '#aaaaaa',
trafficColorNormal: '#4caf50',
trafficColorSlow: '#ff9800',
trafficColorTrafficJam: '#f44336',
}}
/>Or use the built-in themes:
import { DefaultNavigationTheme, DarkNavigationTheme } from 'react-native-google-nav';
<GoogleNavView theme={DarkNavigationTheme} />Test navigation without real GPS movement. After setting destinations:
// Start simulated driving along the route
navRef.current?.startSimulation();
// Stop simulation (e.g., at a delivery stop for OTP verification)
navRef.current?.stopSimulation();
// Resume after OTP — rebuilds route with remaining waypoints
navRef.current?.startSimulation();Simulation speed varies by travel mode for realistic testing.
- Ensure
GoogleNavProviderwraps your navigation screen - Verify your API key has the Navigation SDK enabled
- Check that the user accepted the Terms & Conditions dialog
- This is expected briefly when simulation is paused. It resolves when simulation resumes.
- Run
cd ios && pod install --repo-update - Ensure your Podfile has
platform :ios, '16.0'or higher
- Ensure
minSdkVersionis at least 24 - Ensure
compileSdkVersionis at least 36 - The Navigation SDK requires Google Play Services
- Ensure the Routes API is enabled on your API key
- Walking and cycling modes do not support route optimization
The example/ directory contains a full-featured delivery navigation app demonstrating:
- Multi-stop route planning with place search
- Route optimization
- Turn-by-turn navigation with simulation
- OTP delivery verification at each stop
- Stop skipping/cancellation with index management
- Background location tracking
- Delivery summary screen
To run the example:
cd example
npm install
# Add your API keys to example/src/config.ts and example/app.json
npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:androidSee the contributing guide for development workflow and pull request guidelines.
MIT
Made with create-react-native-library