npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

capacitor-google-navigation

v0.2.2

Published

Google maps turn by turn navigation for capcitor

Readme

capacitor-google-navigation

A Capacitor 8 plugin for Google Navigation SDK — turn-by-turn navigation inside your iOS and Android app.

Native only. This plugin has no web implementation. It must be run on a physical or emulated iOS/Android device.


Requirements

Google Cloud Console

  1. Open or create a project at console.cloud.google.com
  2. Enable the Navigation SDK API
  3. Enable billing on your project
  4. Create an API key under APIs & Services → Credentials

iOS

  • iOS 15.0+
  • Xcode 14+
  • CocoaPods — Swift Package Manager is not supported (Google Navigation SDK has no SPM distribution)

Android

  • Android API 24 (Android 7.0)+
  • Google Play Services on the device

Installation

npm install capacitor-google-navigation
npx cap sync

iOS Setup

1. Install pods

cd ios/App
pod install

The GoogleNavigation ~> 9.0 pod is declared in the plugin's podspec and is pulled in automatically.

2. Add location permissions to Info.plist

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app uses your location for turn-by-turn navigation.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app uses your location for navigation, including in the background.</string>

3. Register your API key

Production (recommended)

Store the key in Info.plist under the key GoogleNavigationAPIKey. The plugin reads it automatically — no key needs to be passed from JS.

<key>GoogleNavigationAPIKey</key>
<string>YOUR_IOS_API_KEY</string>

Then call initialize() with no key:

await GoogleNavigation.initialize({});

Keep the key out of source control by injecting it at build time via an .xcconfig file:

// Config.xcconfig  (gitignored)
GOOGLE_NAV_API_KEY = AIzaSy...
<!-- Info.plist -->
<key>GoogleNavigationAPIKey</key>
<string>$(GOOGLE_NAV_API_KEY)</string>

Development only

You can pass the key directly from JS for quick local testing. Do not ship this in production — the key will be visible in your compiled JS bundle.

await GoogleNavigation.initialize({ apiKey: 'YOUR_IOS_API_KEY' });

Restrict your API key

In Google Cloud Console → APIs & Services → Credentials, add an iOS app restriction with your app's bundle ID. This ensures the key is rejected if extracted and used outside your app.


Android Setup

1. Add the API key and permissions to android/app/src/main/AndroidManifest.xml

The Android Navigation SDK reads the key directly from the manifest at startup — it is never passed from JS.

<manifest>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <application>
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${GOOGLE_NAV_API_KEY}" />
    </application>
</manifest>

Keep the key out of source control using gradle.properties (gitignored):

# android/gradle.properties  (gitignored)
GOOGLE_NAV_API_KEY=AIzaSy...

Then in android/app/build.gradle, expose it to the manifest:

android {
    defaultConfig {
        manifestPlaceholders = [GOOGLE_NAV_API_KEY: project.findProperty("GOOGLE_NAV_API_KEY") ?: ""]
    }
}

Restrict the key in Google Cloud Console by adding your app's SHA-1 certificate fingerprint and package name under Android app restrictions.

Important: On Android the Navigation SDK reads the API key from AndroidManifest.xml — not from the apiKey parameter passed to initialize(). The apiKey parameter is used on iOS only.

2. Request location permission at runtime

The plugin does not request permissions itself. You must request ACCESS_FINE_LOCATION before calling initialize(). In an Ionic React app use @capacitor/geolocation or the browser Permissions API:

import { Geolocation } from '@capacitor/geolocation';

await Geolocation.requestPermissions();

How it works

Calling showNavigationView({ show: true }) presents a full-screen native navigation UI on top of your app. Your Ionic/web UI stays alive underneath. Calling showNavigationView({ show: false }) dismisses the native view and restores your app UI.

Call order:

initialize()
    ↓  fires onNavigationReady when SDK is ready
showNavigationView({ show: true })
    ↓  presents native map full-screen
startNavigation({ lat, lng, travelMode })
    ↓  sets destination, begins guidance
    ↓  fires onArrival when user arrives
    ↓  fires onRouteChanged on recalculation
stopNavigation()
    ↓  ends guidance, clears destination
showNavigationView({ show: false })  — or user taps the ✕ close button
    ↓  dismisses native map, fires onNavigationClosed, restores app UI

Usage — Ionic React

1. Create a useGoogleNavigation hook

// src/hooks/useGoogleNavigation.ts
import { useEffect, useRef, useCallback } from 'react';
import { GoogleNavigation } from 'capacitor-google-navigation';
import type { PluginListenerHandle } from 'capacitor-google-navigation';

interface UseNavigationOptions {
  apiKey: string;
  onArrival?: (event: any) => void;
  onRouteChanged?: () => void;
  onNavigationClosed?: () => void;
}

export function useGoogleNavigation({ apiKey, onArrival, onRouteChanged, onNavigationClosed }: UseNavigationOptions) {
  const listeners = useRef<PluginListenerHandle[]>([]);

  useEffect(() => {
    const setup = async () => {
      const readyHandle = await GoogleNavigation.addListener('onNavigationReady', () => {
        console.log('Navigation SDK ready');
      });
      listeners.current.push(readyHandle);

      if (onArrival) {
        const h = await GoogleNavigation.addListener('onArrival', onArrival);
        listeners.current.push(h);
      }

      if (onRouteChanged) {
        const h = await GoogleNavigation.addListener('onRouteChanged', onRouteChanged);
        listeners.current.push(h);
      }

      if (onNavigationClosed) {
        const h = await GoogleNavigation.addListener('onNavigationClosed', onNavigationClosed);
        listeners.current.push(h);
      }

      await GoogleNavigation.initialize({ apiKey });
    };

    setup().catch(console.error);

    return () => {
      listeners.current.forEach(h => h.remove());
      listeners.current = [];
    };
  }, [apiKey]);

  const navigate = useCallback(async (
    latitude: number,
    longitude: number,
    travelMode: 'DRIVING' | 'WALKING' | 'CYCLING' | 'TWO_WHEELER' = 'DRIVING',
  ) => {
    await GoogleNavigation.showNavigationView({ show: true });
    await GoogleNavigation.startNavigation({
      destinationLatitude: latitude,
      destinationLongitude: longitude,
      travelMode,
    });
  }, []);

  const stop = useCallback(async () => {
    await GoogleNavigation.stopNavigation();
    await GoogleNavigation.showNavigationView({ show: false });
  }, []);

  return { navigate, stop };
}

2. Use the hook in a page

// src/pages/NavigationPage.tsx
import React from 'react';
import {
  IonPage,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonButton,
  IonAlert,
} from '@ionic/react';
import { useGoogleNavigation } from '../hooks/useGoogleNavigation';

const NavigationPage: React.FC = () => {
  const [showArrival, setShowArrival] = React.useState(false);

  const { navigate, stop } = useGoogleNavigation({
    apiKey: import.meta.env.VITE_GOOGLE_NAV_API_KEY as string,
    onArrival: () => setShowArrival(true),
    onRouteChanged: () => console.log('Route recalculated'),
    onNavigationClosed: () => console.log('User closed navigation'),
  });

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Navigation</IonTitle>
        </IonToolbar>
      </IonHeader>

      <IonContent className="ion-padding">
        <IonButton
          expand="block"
          onClick={() => navigate(37.7749, -122.4194, 'DRIVING')}
        >
          Navigate to San Francisco
        </IonButton>

        <IonButton expand="block" color="medium" onClick={() => navigate(34.0522, -118.2437, 'WALKING')}>
          Walk to Los Angeles
        </IonButton>

        <IonButton expand="block" color="danger" onClick={stop}>
          Stop Navigation
        </IonButton>
      </IonContent>

      <IonAlert
        isOpen={showArrival}
        header="Arrived!"
        message="You have reached your destination."
        buttons={['OK']}
        onDidDismiss={() => setShowArrival(false)}
      />
    </IonPage>
  );
};

export default NavigationPage;

3. Store your API key in .env

Create a .env file in your app root (never commit this):

VITE_GOOGLE_NAV_API_KEY=AIzaSy...

Usage — Vanilla TypeScript / JavaScript

import { GoogleNavigation } from 'capacitor-google-navigation';

// 1. Attach event listeners first
const readyHandle = await GoogleNavigation.addListener('onNavigationReady', () => {
  console.log('SDK ready');
});

const arrivalHandle = await GoogleNavigation.addListener('onArrival', (event) => {
  console.log('Arrived:', event);
});

const routeHandle = await GoogleNavigation.addListener('onRouteChanged', () => {
  console.log('Route recalculated');
});

const closedHandle = await GoogleNavigation.addListener('onNavigationClosed', () => {
  // Fired when the user taps the ✕ close button on the native navigation view
  console.log('Navigation closed by user');
});

// 2. Initialize the SDK (fires onNavigationReady when done)
await GoogleNavigation.initialize({ apiKey: 'YOUR_API_KEY' });

// 3. Show the native navigation view
await GoogleNavigation.showNavigationView({ show: true });

// 4. Start navigation
await GoogleNavigation.startNavigation({
  destinationLatitude: 37.7749,
  destinationLongitude: -122.4194,
  travelMode: 'DRIVING', // DRIVING | WALKING | CYCLING | TWO_WHEELER
});

// 5. Stop guidance and dismiss
await GoogleNavigation.stopNavigation();
await GoogleNavigation.showNavigationView({ show: false });

// 6. Clean up
await readyHandle.remove();
await arrivalHandle.remove();
await routeHandle.remove();
// or:
await GoogleNavigation.removeAllListeners();

API

initialize(...)

initialize(options: { apiKey: string; }) => Promise<{ success: boolean; }>

Initialize the Navigation SDK with API key

| Param | Type | | ------------- | -------------------------------- | | options | { apiKey: string; } |

Returns: Promise<{ success: boolean; }>


startNavigation(...)

startNavigation(options: { destinationLatitude: number; destinationLongitude: number; travelMode?: 'DRIVING' | 'WALKING' | 'CYCLING' | 'TWO_WHEELER'; }) => Promise<{ success: boolean; }>

Start navigation to a destination

| Param | Type | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | options | { destinationLatitude: number; destinationLongitude: number; travelMode?: 'DRIVING' | 'WALKING' | 'CYCLING' | 'TWO_WHEELER'; } |

Returns: Promise<{ success: boolean; }>


stopNavigation()

stopNavigation() => Promise<{ success: boolean; }>

Stop navigation

Returns: Promise<{ success: boolean; }>


showNavigationView(...)

showNavigationView(options: { show: boolean; }) => Promise<{ success: boolean; }>

Show/hide navigation view

| Param | Type | | ------------- | -------------------------------- | | options | { show: boolean; } |

Returns: Promise<{ success: boolean; }>


addListener(...)

addListener(eventName: 'onArrival' | 'onRouteChanged' | 'onNavigationReady' | 'onNavigationClosed', listenerFunc: (event: any) => void) => Promise<PluginListenerHandle>

Add listener for navigation events

| Param | Type | | ------------------ | ----------------------------------------------------------------- | | eventName | 'onArrival' | 'onRouteChanged' | 'onNavigationReady' | 'onNavigationClosed' | | listenerFunc | (event: any) => void |

Returns: Promise<PluginListenerHandle>


removeAllListeners()

removeAllListeners() => Promise<void>

Remove all listeners


Interfaces

PluginListenerHandle

| Prop | Type | | ------------ | ----------------------------------------- | | remove | () => Promise<void> |


Events

| Event | Payload | Fired when | |-------|---------|-----------| | onNavigationReady | {} | SDK has initialized and the navigator is available | | onArrival | { latitude, longitude, title } | User arrives at the destination waypoint | | onRouteChanged | {} | The route is recalculated (traffic, missed turn, etc.) | | onNavigationClosed | {} | User tapped the ✕ close button on the native navigation view (iOS) |


Platform notes

| Feature | iOS | Android | |---------|-----|---------| | Turn-by-turn guidance | ✅ | ✅ | | Full-screen native UI | ✅ | ✅ | | onArrival event | ✅ | ✅ | | onRouteChanged event | ✅ | ✅ | | API key via initialize() | ✅ | ⚠️ Manifest only | | Web | ❌ | ❌ | | Swift Package Manager | ❌ | — |


Troubleshooting

showNavigationView must be called before startNavigation The navigator instance is only available after the native navigation view is presented. Calling startNavigation without first calling showNavigationView({ show: true }) will return an error.

Blank map on Android All NavigationView lifecycle events must be delegated — the plugin handles this via NavigationFragment. If you see a blank map, check that your Activity extends AppCompatActivity (Capacitor does this by default).

initialize() fails on Android The Android Navigation SDK validates the API key from AndroidManifest.xml. Ensure the key is present and the Navigation SDK is enabled in your Google Cloud project with billing active.

iOS — "This app has attempted to access privacy-sensitive data" Add both NSLocationWhenInUseUsageDescription and NSLocationAlwaysAndWhenInUseUsageDescription to Info.plist before calling initialize().

iOS — App crashes with "Invalid parameter not satisfying: CLClientIsBackgroundable" The Navigation SDK requires background location capability to track position during guidance. In Xcode:

  1. Select your app target → Signing & Capabilities+ CapabilityBackground Modes
  2. Check Location updates

Also ensure all three keys are present in Info.plist:

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app uses your location for navigation.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app uses your location for navigation, including in the background.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app uses your location for navigation, including in the background.</string>

iOS — "This application has been blocked by the Google Navigation SDK" The Navigation SDK requires explicit enrollment — it is not available to all Google Cloud projects by default. Ensure:

  1. The Navigation SDK for iOS is enabled under APIs & Services → Library in Google Cloud Console
  2. Your project has been granted access (you may need to request it via the Navigation SDK get started page)
  3. Billing is active on the project

Android — Duplicate class build error The Navigation SDK bundles Maps SDK and Location classes internally. Do not add play-services-maps or play-services-location as separate dependencies in your app or any plugin — they will conflict with the classes already inside the Navigation SDK AAR and cause a dex merge failure.

If you see an error like:

Duplicate class com.google.android.gms.maps.* found in modules ...

Check android/app/build.gradle and any plugin build.gradle files and remove any explicit play-services-maps or play-services-location dependencies.

Android — core library desugaring build error The Navigation SDK requires core library desugaring to be enabled in the consuming app. If you see:

Dependency 'com.google.android.libraries.navigation:navigation:x.x.x' requires
core library desugaring to be enabled for :app.

Add the following to your app's android/app/build.gradle:

android {
    compileOptions {
        coreLibraryDesugaringEnabled true
    }
}

dependencies {
    coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.4"
}

CocoaPods not found / pod install fails Make sure CocoaPods is installed (sudo gem install cocoapods) and run npx cap sync before pod install.

iOS — cap sync fails with "transitive dependencies that include statically linked binaries" The GoogleNavigation SDK is distributed as a static XCFramework, which conflicts with the default dynamic use_frameworks! directive. In your app's ios/App/Podfile, change:

use_frameworks!

to:

use_frameworks! :linkage => :static

Then re-run pod install and npx cap sync.


License

MIT