capacitor-google-navigation 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapacitorGoogleNavigation'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '15.0'
15
+ s.dependency 'Capacitor'
16
+ s.dependency 'GoogleNavigation', '~> 9.0'
17
+ s.swift_version = '5.1'
18
+ end
package/Package.swift ADDED
@@ -0,0 +1,28 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CapacitorGoogleNavigation",
6
+ platforms: [.iOS(.v15)],
7
+ products: [
8
+ .library(
9
+ name: "CapacitorGoogleNavigation",
10
+ targets: ["GoogleNavigationPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "GoogleNavigationPlugin",
18
+ dependencies: [
19
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
+ .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ ],
22
+ path: "ios/Sources/GoogleNavigationPlugin"),
23
+ .testTarget(
24
+ name: "GoogleNavigationPluginTests",
25
+ dependencies: ["GoogleNavigationPlugin"],
26
+ path: "ios/Tests/GoogleNavigationPluginTests")
27
+ ]
28
+ )
package/README.md ADDED
@@ -0,0 +1,486 @@
1
+ # capacitor-google-navigation
2
+
3
+ A Capacitor 8 plugin for Google Navigation SDK — turn-by-turn navigation inside your iOS and Android app.
4
+
5
+ > **Native only.** This plugin has no web implementation. It must be run on a physical or emulated iOS/Android device.
6
+
7
+ ---
8
+
9
+ ## Requirements
10
+
11
+ ### Google Cloud Console
12
+
13
+ 1. Open or create a project at [console.cloud.google.com](https://console.cloud.google.com)
14
+ 2. Enable the **Navigation SDK** API
15
+ 3. Enable **billing** on your project
16
+ 4. Create an API key under **APIs & Services → Credentials**
17
+
18
+ ### iOS
19
+
20
+ - iOS 15.0+
21
+ - Xcode 14+
22
+ - CocoaPods — **Swift Package Manager is not supported** (Google Navigation SDK has no SPM distribution)
23
+
24
+ ### Android
25
+
26
+ - Android API 24 (Android 7.0)+
27
+ - Google Play Services on the device
28
+
29
+ ---
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install capacitor-google-navigation
35
+ npx cap sync
36
+ ```
37
+
38
+ ---
39
+
40
+ ## iOS Setup
41
+
42
+ ### 1. Install pods
43
+
44
+ ```bash
45
+ cd ios/App
46
+ pod install
47
+ ```
48
+
49
+ The `GoogleNavigation ~> 9.0` pod is declared in the plugin's podspec and is pulled in automatically.
50
+
51
+ ### 2. Add location permissions to `Info.plist`
52
+
53
+ ```xml
54
+ <key>NSLocationWhenInUseUsageDescription</key>
55
+ <string>This app uses your location for turn-by-turn navigation.</string>
56
+ <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
57
+ <string>This app uses your location for navigation, including in the background.</string>
58
+ ```
59
+
60
+ ### 3. Register your API key in `AppDelegate.swift`
61
+
62
+ The iOS SDK requires the key to be provided before any map or navigator is created.
63
+
64
+ ```swift
65
+ import UIKit
66
+ import Capacitor
67
+ import GoogleNavigation
68
+
69
+ @UIApplicationMain
70
+ class AppDelegate: UIResponder, UIApplicationDelegate {
71
+
72
+ func application(
73
+ _ application: UIApplication,
74
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
75
+ ) -> Bool {
76
+ GMSServices.provideAPIKey("YOUR_IOS_API_KEY")
77
+ return true
78
+ }
79
+ }
80
+ ```
81
+
82
+ > You can also pass the key via `GoogleNavigation.initialize({ apiKey })` at runtime — the plugin calls `GMSServices.provideAPIKey()` for you. Either approach works; calling it in `AppDelegate` is the earlier-initialization option.
83
+
84
+ ---
85
+
86
+ ## Android Setup
87
+
88
+ ### 1. Add the API key and permissions to `android/app/src/main/AndroidManifest.xml`
89
+
90
+ ```xml
91
+ <manifest>
92
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
93
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
94
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
95
+
96
+ <application>
97
+ <!-- Required: Navigation SDK reads this at startup -->
98
+ <meta-data
99
+ android:name="com.google.android.geo.API_KEY"
100
+ android:value="YOUR_ANDROID_API_KEY" />
101
+ </application>
102
+ </manifest>
103
+ ```
104
+
105
+ > **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.
106
+
107
+ ### 2. Request location permission at runtime
108
+
109
+ 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:
110
+
111
+ ```typescript
112
+ import { Geolocation } from '@capacitor/geolocation';
113
+
114
+ await Geolocation.requestPermissions();
115
+ ```
116
+
117
+ ---
118
+
119
+ ## How it works
120
+
121
+ 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.
122
+
123
+ **Call order:**
124
+
125
+ ```
126
+ initialize()
127
+ ↓ fires onNavigationReady when SDK is ready
128
+ showNavigationView({ show: true })
129
+ ↓ presents native map full-screen
130
+ startNavigation({ lat, lng, travelMode })
131
+ ↓ sets destination, begins guidance
132
+ ↓ fires onArrival when user arrives
133
+ ↓ fires onRouteChanged on recalculation
134
+ stopNavigation()
135
+ ↓ ends guidance, clears destination
136
+ showNavigationView({ show: false })
137
+ ↓ dismisses native map, restores app UI
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Usage — Ionic React
143
+
144
+ ### 1. Create a `useGoogleNavigation` hook
145
+
146
+ ```typescript
147
+ // src/hooks/useGoogleNavigation.ts
148
+ import { useEffect, useRef, useCallback } from 'react';
149
+ import { GoogleNavigation } from 'capacitor-google-navigation';
150
+ import type { PluginListenerHandle } from 'capacitor-google-navigation';
151
+
152
+ interface UseNavigationOptions {
153
+ apiKey: string;
154
+ onArrival?: (event: any) => void;
155
+ onRouteChanged?: () => void;
156
+ }
157
+
158
+ export function useGoogleNavigation({ apiKey, onArrival, onRouteChanged }: UseNavigationOptions) {
159
+ const listeners = useRef<PluginListenerHandle[]>([]);
160
+
161
+ useEffect(() => {
162
+ const setup = async () => {
163
+ const readyHandle = await GoogleNavigation.addListener('onNavigationReady', () => {
164
+ console.log('Navigation SDK ready');
165
+ });
166
+ listeners.current.push(readyHandle);
167
+
168
+ if (onArrival) {
169
+ const h = await GoogleNavigation.addListener('onArrival', onArrival);
170
+ listeners.current.push(h);
171
+ }
172
+
173
+ if (onRouteChanged) {
174
+ const h = await GoogleNavigation.addListener('onRouteChanged', onRouteChanged);
175
+ listeners.current.push(h);
176
+ }
177
+
178
+ await GoogleNavigation.initialize({ apiKey });
179
+ };
180
+
181
+ setup().catch(console.error);
182
+
183
+ return () => {
184
+ listeners.current.forEach(h => h.remove());
185
+ listeners.current = [];
186
+ };
187
+ }, [apiKey]);
188
+
189
+ const navigate = useCallback(async (
190
+ latitude: number,
191
+ longitude: number,
192
+ travelMode: 'DRIVING' | 'WALKING' | 'CYCLING' | 'TWO_WHEELER' = 'DRIVING',
193
+ ) => {
194
+ await GoogleNavigation.showNavigationView({ show: true });
195
+ await GoogleNavigation.startNavigation({
196
+ destinationLatitude: latitude,
197
+ destinationLongitude: longitude,
198
+ travelMode,
199
+ });
200
+ }, []);
201
+
202
+ const stop = useCallback(async () => {
203
+ await GoogleNavigation.stopNavigation();
204
+ await GoogleNavigation.showNavigationView({ show: false });
205
+ }, []);
206
+
207
+ return { navigate, stop };
208
+ }
209
+ ```
210
+
211
+ ### 2. Use the hook in a page
212
+
213
+ ```tsx
214
+ // src/pages/NavigationPage.tsx
215
+ import React from 'react';
216
+ import {
217
+ IonPage,
218
+ IonHeader,
219
+ IonToolbar,
220
+ IonTitle,
221
+ IonContent,
222
+ IonButton,
223
+ IonAlert,
224
+ } from '@ionic/react';
225
+ import { useGoogleNavigation } from '../hooks/useGoogleNavigation';
226
+
227
+ const NavigationPage: React.FC = () => {
228
+ const [showArrival, setShowArrival] = React.useState(false);
229
+
230
+ const { navigate, stop } = useGoogleNavigation({
231
+ apiKey: import.meta.env.VITE_GOOGLE_NAV_API_KEY as string,
232
+ onArrival: () => setShowArrival(true),
233
+ onRouteChanged: () => console.log('Route recalculated'),
234
+ });
235
+
236
+ return (
237
+ <IonPage>
238
+ <IonHeader>
239
+ <IonToolbar>
240
+ <IonTitle>Navigation</IonTitle>
241
+ </IonToolbar>
242
+ </IonHeader>
243
+
244
+ <IonContent className="ion-padding">
245
+ <IonButton
246
+ expand="block"
247
+ onClick={() => navigate(37.7749, -122.4194, 'DRIVING')}
248
+ >
249
+ Navigate to San Francisco
250
+ </IonButton>
251
+
252
+ <IonButton expand="block" color="medium" onClick={() => navigate(34.0522, -118.2437, 'WALKING')}>
253
+ Walk to Los Angeles
254
+ </IonButton>
255
+
256
+ <IonButton expand="block" color="danger" onClick={stop}>
257
+ Stop Navigation
258
+ </IonButton>
259
+ </IonContent>
260
+
261
+ <IonAlert
262
+ isOpen={showArrival}
263
+ header="Arrived!"
264
+ message="You have reached your destination."
265
+ buttons={['OK']}
266
+ onDidDismiss={() => setShowArrival(false)}
267
+ />
268
+ </IonPage>
269
+ );
270
+ };
271
+
272
+ export default NavigationPage;
273
+ ```
274
+
275
+ ### 3. Store your API key in `.env`
276
+
277
+ Create a `.env` file in your app root (never commit this):
278
+
279
+ ```
280
+ VITE_GOOGLE_NAV_API_KEY=AIzaSy...
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Usage — Vanilla TypeScript / JavaScript
286
+
287
+ ```typescript
288
+ import { GoogleNavigation } from 'capacitor-google-navigation';
289
+
290
+ // 1. Attach event listeners first
291
+ const readyHandle = await GoogleNavigation.addListener('onNavigationReady', () => {
292
+ console.log('SDK ready');
293
+ });
294
+
295
+ const arrivalHandle = await GoogleNavigation.addListener('onArrival', (event) => {
296
+ console.log('Arrived:', event);
297
+ });
298
+
299
+ const routeHandle = await GoogleNavigation.addListener('onRouteChanged', () => {
300
+ console.log('Route recalculated');
301
+ });
302
+
303
+ // 2. Initialize the SDK (fires onNavigationReady when done)
304
+ await GoogleNavigation.initialize({ apiKey: 'YOUR_API_KEY' });
305
+
306
+ // 3. Show the native navigation view
307
+ await GoogleNavigation.showNavigationView({ show: true });
308
+
309
+ // 4. Start navigation
310
+ await GoogleNavigation.startNavigation({
311
+ destinationLatitude: 37.7749,
312
+ destinationLongitude: -122.4194,
313
+ travelMode: 'DRIVING', // DRIVING | WALKING | CYCLING | TWO_WHEELER
314
+ });
315
+
316
+ // 5. Stop guidance and dismiss
317
+ await GoogleNavigation.stopNavigation();
318
+ await GoogleNavigation.showNavigationView({ show: false });
319
+
320
+ // 6. Clean up
321
+ await readyHandle.remove();
322
+ await arrivalHandle.remove();
323
+ await routeHandle.remove();
324
+ // or:
325
+ await GoogleNavigation.removeAllListeners();
326
+ ```
327
+
328
+ ---
329
+
330
+ ## API
331
+
332
+ <!-- docgen-api -->
333
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
334
+
335
+ ### initialize(...)
336
+
337
+ ```typescript
338
+ initialize(options: { apiKey: string; }) => Promise<{ success: boolean; }>
339
+ ```
340
+
341
+ Initialize the Navigation SDK with API key
342
+
343
+ | Param | Type |
344
+ | ------------- | -------------------------------- |
345
+ | **`options`** | <code>{ apiKey: string; }</code> |
346
+
347
+ **Returns:** <code>Promise&lt;{ success: boolean; }&gt;</code>
348
+
349
+ --------------------
350
+
351
+
352
+ ### startNavigation(...)
353
+
354
+ ```typescript
355
+ startNavigation(options: { destinationLatitude: number; destinationLongitude: number; travelMode?: 'DRIVING' | 'WALKING' | 'CYCLING' | 'TWO_WHEELER'; }) => Promise<{ success: boolean; }>
356
+ ```
357
+
358
+ Start navigation to a destination
359
+
360
+ | Param | Type |
361
+ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
362
+ | **`options`** | <code>{ destinationLatitude: number; destinationLongitude: number; travelMode?: 'DRIVING' \| 'WALKING' \| 'CYCLING' \| 'TWO_WHEELER'; }</code> |
363
+
364
+ **Returns:** <code>Promise&lt;{ success: boolean; }&gt;</code>
365
+
366
+ --------------------
367
+
368
+
369
+ ### stopNavigation()
370
+
371
+ ```typescript
372
+ stopNavigation() => Promise<{ success: boolean; }>
373
+ ```
374
+
375
+ Stop navigation
376
+
377
+ **Returns:** <code>Promise&lt;{ success: boolean; }&gt;</code>
378
+
379
+ --------------------
380
+
381
+
382
+ ### showNavigationView(...)
383
+
384
+ ```typescript
385
+ showNavigationView(options: { show: boolean; }) => Promise<{ success: boolean; }>
386
+ ```
387
+
388
+ Show/hide navigation view
389
+
390
+ | Param | Type |
391
+ | ------------- | -------------------------------- |
392
+ | **`options`** | <code>{ show: boolean; }</code> |
393
+
394
+ **Returns:** <code>Promise&lt;{ success: boolean; }&gt;</code>
395
+
396
+ --------------------
397
+
398
+
399
+ ### addListener(...)
400
+
401
+ ```typescript
402
+ addListener(eventName: 'onArrival' | 'onRouteChanged' | 'onNavigationReady', listenerFunc: (event: any) => void) => Promise<PluginListenerHandle>
403
+ ```
404
+
405
+ Add listener for navigation events
406
+
407
+ | Param | Type |
408
+ | ------------------ | ----------------------------------------------------------------- |
409
+ | **`eventName`** | <code>'onArrival' \| 'onRouteChanged' \| 'onNavigationReady'</code> |
410
+ | **`listenerFunc`** | <code>(event: any) =&gt; void</code> |
411
+
412
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
413
+
414
+ --------------------
415
+
416
+
417
+ ### removeAllListeners()
418
+
419
+ ```typescript
420
+ removeAllListeners() => Promise<void>
421
+ ```
422
+
423
+ Remove all listeners
424
+
425
+ --------------------
426
+
427
+
428
+ ### Interfaces
429
+
430
+
431
+ #### PluginListenerHandle
432
+
433
+ | Prop | Type |
434
+ | ------------ | ----------------------------------------- |
435
+ | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
436
+
437
+ <!-- docgen-api-end -->
438
+
439
+ ---
440
+
441
+ ## Events
442
+
443
+ | Event | Payload | Fired when |
444
+ |-------|---------|-----------|
445
+ | `onNavigationReady` | `{}` | SDK has initialized and the navigator is available |
446
+ | `onArrival` | `{ latitude, longitude, title }` | User arrives at the destination waypoint |
447
+ | `onRouteChanged` | `{}` | The route is recalculated (traffic, missed turn, etc.) |
448
+
449
+ ---
450
+
451
+ ## Platform notes
452
+
453
+ | Feature | iOS | Android |
454
+ |---------|-----|---------|
455
+ | Turn-by-turn guidance | ✅ | ✅ |
456
+ | Full-screen native UI | ✅ | ✅ |
457
+ | `onArrival` event | ✅ | ✅ |
458
+ | `onRouteChanged` event | ✅ | ✅ |
459
+ | API key via `initialize()` | ✅ | ⚠️ Manifest only |
460
+ | Web | ❌ | ❌ |
461
+ | Swift Package Manager | ❌ | — |
462
+
463
+ ---
464
+
465
+ ## Troubleshooting
466
+
467
+ **`showNavigationView` must be called before `startNavigation`**
468
+ 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.
469
+
470
+ **Blank map on Android**
471
+ 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).
472
+
473
+ **`initialize()` fails on Android**
474
+ 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.
475
+
476
+ **iOS — "This app has attempted to access privacy-sensitive data"**
477
+ Add both `NSLocationWhenInUseUsageDescription` and `NSLocationAlwaysAndWhenInUseUsageDescription` to `Info.plist` before calling `initialize()`.
478
+
479
+ **CocoaPods not found / pod install fails**
480
+ Make sure CocoaPods is installed (`sudo gem install cocoapods`) and run `npx cap sync` before `pod install`.
481
+
482
+ ---
483
+
484
+ ## License
485
+
486
+ MIT
@@ -0,0 +1,59 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
6
+ }
7
+
8
+ buildscript {
9
+ repositories {
10
+ google()
11
+ mavenCentral()
12
+ }
13
+ dependencies {
14
+ classpath 'com.android.tools.build:gradle:8.13.0'
15
+ }
16
+ }
17
+
18
+ apply plugin: 'com.android.library'
19
+
20
+ android {
21
+ namespace = "com.attributeai.navigation"
22
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
26
+ versionCode 1
27
+ versionName "1.0"
28
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ }
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ lintOptions {
37
+ abortOnError = false
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_21
41
+ targetCompatibility JavaVersion.VERSION_21
42
+ }
43
+ }
44
+
45
+ repositories {
46
+ google()
47
+ mavenCentral()
48
+ }
49
+
50
+
51
+ dependencies {
52
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
53
+ implementation project(':capacitor-android')
54
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
+ implementation 'com.google.android.libraries.navigation:navigation:6.0.0'
56
+ testImplementation "junit:junit:$junitVersion"
57
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
58
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
59
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>