@baeckerherz/expo-mapbox-navigation 1.0.3 → 1.0.5

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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Expo module for turn-by-turn navigation on iOS and Android** using [Mapbox Navigation SDK v3](https://docs.mapbox.com/ios/navigation/guides/) (iOS) / [Android](https://docs.mapbox.com/android/navigation/guides/). Single `MapboxNavigation` component, Expo config plugin for credentials, no vendored binaries. Minimal alternative to existing community wrappers.
6
6
 
7
- > **Alpha** — Working on **iOS and Android** in our project builds (Expo prebuild, EAS Build). You may use it, but we need more feedback and real-world testing. APIs may change. Not recommended for production without your own testing. **Contributions welcome.** **We’d love to hear from individuals and companies using this package** — open an issue or reach out.
7
+ > **Alpha** — iOS is working in our builds; Android is not yet working (dependency resolution / Mapbox Maven). We need help to get Android over the line. APIs may change. **Contributions welcome.** Open an issue or reach out.
8
8
 
9
9
  **Package summary:** Expo config plugin + native module; Mapbox Navigation SDK v3; iOS (SPM) and Android (Maven, drop-in NavigationView); turn-by-turn driving/walking/cycling; requires Expo ≥51, React Native ≥0.74, Mapbox public + secret tokens; alpha stage; TypeScript API via `MapboxNavigation` component.
10
10
 
@@ -25,8 +25,17 @@
25
25
  ## Prerequisites
26
26
 
27
27
  - [Mapbox account](https://account.mapbox.com/) with Navigation SDK access
28
- - **Public token** (`pk.xxx`) and **secret/download token** (`sk.xxx`)
29
- - **iOS:** Add Mapbox credentials to `~/.netrc` for SPM:
28
+
29
+ Mapbox requires **two tokens** (create both in your [Mapbox tokens page](https://account.mapbox.com/access-tokens/)):
30
+
31
+ | Token | Prefix | Purpose |
32
+ |-------|--------|---------|
33
+ | **Public (access) token** | `pk.xxx` | Used by the app at runtime: map tiles, Directions API, voice, etc. Set as `mapboxAccessToken` in the plugin. |
34
+ | **Secret (downloads) token** | `sk.xxx` | Used only at **build time**: Gradle (Android) and SPM (iOS) use it to download the Navigation SDK from Mapbox. Must have **Downloads:Read** scope. Not used by the app at runtime. |
35
+
36
+ The same **secret token** is used for both platforms. For EAS Build, one EAS secret (e.g. `MAPBOX_DOWNLOADS_TOKEN` or `MAPBOX_SECRET_TOKEN`) can back both: iOS needs it in `~/.netrc` for SPM; Android needs it in `gradle.properties` (the plugin writes it from `mapboxSecretToken`) and, when using centralized repo resolution, as env var `MAPBOX_DOWNLOADS_TOKEN`.
37
+
38
+ - **iOS:** Add the secret token to `~/.netrc` so SPM can download the SDK (local dev and EAS Build):
30
39
 
31
40
  ```plaintext
32
41
  machine api.mapbox.com
@@ -34,7 +43,7 @@
34
43
  password YOUR_SECRET_TOKEN
35
44
  ```
36
45
 
37
- - **Android:** The plugin writes `mapboxSecretToken` to `android/gradle.properties` as `MAPBOX_DOWNLOADS_TOKEN` so Maven can download the SDK. Use the same plugin config as for iOS.
46
+ - **Android:** The plugin writes `mapboxSecretToken` to `android/gradle.properties` as `MAPBOX_DOWNLOADS_TOKEN` so Maven can download the SDK. For EAS Build, also set `MAPBOX_DOWNLOADS_TOKEN` as an EAS secret so the build can authenticate.
38
47
 
39
48
  ## Installation
40
49
 
@@ -155,17 +164,20 @@ Existing wrappers have major drawbacks:
155
164
 
156
165
  ## Status
157
166
 
158
- **Alpha.** Actively used in Bäckerherz project builds (iOS and Android). Goals:
167
+ **Alpha.** Platform status:
168
+
169
+ | Platform | Status | Notes |
170
+ |----------|--------|-------|
171
+ | **iOS** | Working (Alpha) | SPM via config plugin; tested in our project builds. |
172
+ | **Android** | Not yet working | Mapbox Maven / dependency resolution issues; help wanted to fix. |
159
173
 
160
- 1. Reliable SPM injection with Xcode + CocoaPods
161
- 2. Sufficient drop-in NavigationView/NavigationViewController integration
162
- 3. Event bridging for required use cases
174
+ Goals: reliable SPM injection (iOS), drop-in NavigationView/NavigationViewController, event bridging. We want more feedback and testing. For prerelease we may publish under the `alpha` npm tag.
163
175
 
164
- We want more feedback and testing from the community. Install with `npx expo install @baeckerherz/expo-mapbox-navigation`; for prerelease channels we may publish under the `alpha` npm tag.
176
+ **Help wanted** especially to get Android builds working (Gradle, Mapbox repo auth, or switching to `ui-components` if the drop-in artifact is unavailable).
165
177
 
166
178
  **Known risks**
167
179
 
168
- - **Android:** Drop-in `NavigationView` may need more config for full parity with iOS.
180
+ - **Android:** Build and dependency resolution need community input.
169
181
  - **Licensing:** Mapbox Navigation SDK requires a commercial Mapbox license; this wrapper does not change that.
170
182
 
171
183
  ## Contributing
@@ -74,7 +74,7 @@ dependencies {
74
74
  implementation project(':expo-modules-core')
75
75
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}"
76
76
 
77
- // Mapbox Navigation SDK v3 - drop-in UI
77
+ // Mapbox Navigation SDK v3 - core, drop-in UI, and maps
78
78
  implementation "com.mapbox.navigationcore:android:3.5.0"
79
79
  implementation "com.mapbox.navigationcore:dropin:3.5.0"
80
80
  implementation "com.mapbox.navigationcore:ui-maps:3.5.0"
@@ -13,6 +13,7 @@ import com.mapbox.navigation.core.trip.session.RouteProgressObserver
13
13
  import com.mapbox.navigation.core.trip.session.OffRouteObserver
14
14
  import com.mapbox.navigation.core.directions.session.RoutesObserver
15
15
  import com.mapbox.navigation.dropin.NavigationView
16
+ import com.mapbox.navigation.core.directions.session.RoutesSetCallback
16
17
  import com.mapbox.api.directions.v5.DirectionsCriteria
17
18
  import com.mapbox.api.directions.v5.models.RouteOptions
18
19
 
@@ -45,12 +46,40 @@ class ExpoMapboxNavigationView(
45
46
  private var hasStartedNavigation = false
46
47
 
47
48
  fun setCoordinates(raw: List<Map<String, Double>>) {
48
- coordinates = raw.mapNotNull { map ->
49
+ val newCoords = raw.mapNotNull { map ->
49
50
  val lat = map["latitude"] ?: return@mapNotNull null
50
51
  val lng = map["longitude"] ?: return@mapNotNull null
51
52
  Point.fromLngLat(lng, lat)
52
53
  }
53
- startNavigationIfReady()
54
+ val isRouteChange = hasStartedNavigation && coordinatesChanged(newCoords)
55
+ if (isRouteChange) {
56
+ removeCurrentNavigationView { startNavigationIfReady() }
57
+ }
58
+ coordinates = newCoords
59
+ if (!isRouteChange) {
60
+ startNavigationIfReady()
61
+ }
62
+ }
63
+
64
+ private fun coordinatesChanged(newCoords: List<Point>): Boolean {
65
+ if (coordinates.size != newCoords.size) return true
66
+ return coordinates.zip(newCoords).any { (a, b) ->
67
+ a.latitude() != b.latitude() || a.longitude() != b.longitude()
68
+ }
69
+ }
70
+
71
+ private fun removeCurrentNavigationView(onCleared: (() -> Unit)? = null) {
72
+ navigationView?.let { removeView(it) }
73
+ navigationView = null
74
+ hasStartedNavigation = false
75
+ val nav = MapboxNavigationApp.current()
76
+ if (nav != null) {
77
+ nav.setNavigationRoutes(emptyList(), 0, RoutesSetCallback { _ ->
78
+ post { onCleared?.invoke() }
79
+ })
80
+ } else {
81
+ post { onCleared?.invoke() }
82
+ }
54
83
  }
55
84
 
56
85
  private fun startNavigationIfReady() {
@@ -158,7 +187,6 @@ class ExpoMapboxNavigationView(
158
187
 
159
188
  override fun onDetachedFromWindow() {
160
189
  super.onDetachedFromWindow()
161
- navigationView?.let { removeView(it) }
162
- navigationView = null
190
+ removeCurrentNavigationView()
163
191
  }
164
192
  }
@@ -156,13 +156,39 @@ class ExpoMapboxNavigationView: ExpoView {
156
156
  // MARK: Props
157
157
 
158
158
  func setCoordinates(_ raw: [[String: Double]]) {
159
- coordinates = raw.compactMap { dict in
159
+ let newCoords = raw.compactMap { dict -> CLLocationCoordinate2D? in
160
160
  guard let lat = dict["latitude"], let lng = dict["longitude"] else {
161
161
  return nil
162
162
  }
163
163
  return CLLocationCoordinate2D(latitude: lat, longitude: lng)
164
164
  }
165
- startNavigationIfReady()
165
+ let isRouteChange = hasStartedNavigation && coordinatesDiffer(from: newCoords)
166
+ if isRouteChange {
167
+ removeCurrentNavigation()
168
+ }
169
+ coordinates = newCoords
170
+ if isRouteChange {
171
+ DispatchQueue.main.async { [weak self] in
172
+ self?.startNavigationIfReady()
173
+ }
174
+ } else {
175
+ startNavigationIfReady()
176
+ }
177
+ }
178
+
179
+ private func coordinatesDiffer(from newCoords: [CLLocationCoordinate2D]) -> Bool {
180
+ guard coordinates.count == newCoords.count else { return true }
181
+ return zip(coordinates, newCoords).contains { a, b in
182
+ a.latitude != b.latitude || a.longitude != b.longitude
183
+ }
184
+ }
185
+
186
+ private func removeCurrentNavigation() {
187
+ navigationViewController?.willMove(toParent: nil)
188
+ navigationViewController?.view.removeFromSuperview()
189
+ navigationViewController?.removeFromParent()
190
+ navigationViewController = nil
191
+ hasStartedNavigation = false
166
192
  }
167
193
 
168
194
  // MARK: Layout
@@ -308,9 +334,7 @@ class ExpoMapboxNavigationView: ExpoView {
308
334
  // MARK: Cleanup
309
335
 
310
336
  deinit {
311
- navigationViewController?.willMove(toParent: nil)
312
- navigationViewController?.view.removeFromSuperview()
313
- navigationViewController?.removeFromParent()
337
+ removeCurrentNavigation()
314
338
  }
315
339
  }
316
340
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baeckerherz/expo-mapbox-navigation",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Expo module for turn-by-turn navigation (Mapbox Navigation SDK v3) on iOS and Android. Alpha. Single MapboxNavigation component, Expo config plugin.",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const config_plugins_1 = require("@expo/config-plugins");
4
4
  const withMapboxNavPodfile_1 = require("./withMapboxNavPodfile");
5
5
  const withMapboxNavGradle_1 = require("./withMapboxNavGradle");
6
+ const withMapboxNavSettingsGradle_1 = require("./withMapboxNavSettingsGradle");
6
7
  const withMapboxNavGradleProperties_1 = require("./withMapboxNavGradleProperties");
7
8
  const withMapboxNavigation = (config, { mapboxAccessToken, mapboxSecretToken, navigationSdkVersion = "3.5.0", }) => {
8
9
  if (!mapboxAccessToken) {
@@ -27,8 +28,9 @@ const withMapboxNavigation = (config, { mapboxAccessToken, mapboxSecretToken, na
27
28
  // iOS: Adds Mapbox Navigation SPM to the Pods project, sets search paths,
28
29
  // and adds a script phase to strip duplicate xcframework signatures.
29
30
  config = (0, withMapboxNavPodfile_1.withMapboxNavPodfile)(config, { navigationSdkVersion });
30
- // Android: Mapbox Maven repository and optional token for SDK download
31
+ // Android: Mapbox Maven repository (build.gradle and settings.gradle) and optional token
31
32
  config = (0, withMapboxNavGradle_1.withMapboxNavGradle)(config);
33
+ config = (0, withMapboxNavSettingsGradle_1.withMapboxNavSettingsGradle)(config);
32
34
  config = (0, withMapboxNavGradleProperties_1.withMapboxNavGradleProperties)(config, { mapboxSecretToken });
33
35
  return config;
34
36
  };
@@ -0,0 +1,9 @@
1
+ import { ConfigPlugin } from "@expo/config-plugins";
2
+ /**
3
+ * Adds the Mapbox Maven repository to settings.gradle when the project uses
4
+ * dependencyResolutionManagement (e.g. FAIL_ON_PROJECT_REPOS). In that mode
5
+ * project-level repositories are ignored, so the root build.gradle injection
6
+ * is not enough and we must add the repo here.
7
+ * Uses env MAPBOX_DOWNLOADS_TOKEN so EAS Build and local builds can supply the secret.
8
+ */
9
+ export declare const withMapboxNavSettingsGradle: ConfigPlugin;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withMapboxNavSettingsGradle = void 0;
4
+ const config_plugins_1 = require("@expo/config-plugins");
5
+ /**
6
+ * Adds the Mapbox Maven repository to settings.gradle when the project uses
7
+ * dependencyResolutionManagement (e.g. FAIL_ON_PROJECT_REPOS). In that mode
8
+ * project-level repositories are ignored, so the root build.gradle injection
9
+ * is not enough and we must add the repo here.
10
+ * Uses env MAPBOX_DOWNLOADS_TOKEN so EAS Build and local builds can supply the secret.
11
+ */
12
+ const withMapboxNavSettingsGradle = (config) => {
13
+ return (0, config_plugins_1.withSettingsGradle)(config, (config) => {
14
+ let contents = config.modResults.contents;
15
+ if (contents.includes("api.mapbox.com/downloads/v2/releases/maven")) {
16
+ return config;
17
+ }
18
+ if (!contents.includes("dependencyResolutionManagement") ||
19
+ !contents.includes("repositories {")) {
20
+ return config;
21
+ }
22
+ const mapboxMaven = `
23
+ // @baeckerherz/expo-mapbox-navigation: Mapbox Navigation SDK Maven repository
24
+ maven {
25
+ url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
26
+ authentication {
27
+ basic(BasicAuthentication)
28
+ }
29
+ credentials {
30
+ username = "mapbox"
31
+ password = (System.getenv("MAPBOX_DOWNLOADS_TOKEN") ?: "")
32
+ }
33
+ }`;
34
+ contents = contents.replace(/(repositories\s*\{)/, `$1${mapboxMaven}`);
35
+ config.modResults.contents = contents;
36
+ return config;
37
+ });
38
+ };
39
+ exports.withMapboxNavSettingsGradle = withMapboxNavSettingsGradle;
@@ -1,6 +1,7 @@
1
1
  import { ConfigPlugin, createRunOncePlugin } from "@expo/config-plugins";
2
2
  import { withMapboxNavPodfile } from "./withMapboxNavPodfile";
3
3
  import { withMapboxNavGradle } from "./withMapboxNavGradle";
4
+ import { withMapboxNavSettingsGradle } from "./withMapboxNavSettingsGradle";
4
5
  import { withMapboxNavGradleProperties } from "./withMapboxNavGradleProperties";
5
6
 
6
7
  interface PluginConfig {
@@ -47,8 +48,9 @@ const withMapboxNavigation: ConfigPlugin<PluginConfig> = (
47
48
  // and adds a script phase to strip duplicate xcframework signatures.
48
49
  config = withMapboxNavPodfile(config, { navigationSdkVersion });
49
50
 
50
- // Android: Mapbox Maven repository and optional token for SDK download
51
+ // Android: Mapbox Maven repository (build.gradle and settings.gradle) and optional token
51
52
  config = withMapboxNavGradle(config);
53
+ config = withMapboxNavSettingsGradle(config);
52
54
  config = withMapboxNavGradleProperties(config, { mapboxSecretToken });
53
55
 
54
56
  return config;
@@ -0,0 +1,45 @@
1
+ import { withSettingsGradle, ConfigPlugin } from "@expo/config-plugins";
2
+
3
+ /**
4
+ * Adds the Mapbox Maven repository to settings.gradle when the project uses
5
+ * dependencyResolutionManagement (e.g. FAIL_ON_PROJECT_REPOS). In that mode
6
+ * project-level repositories are ignored, so the root build.gradle injection
7
+ * is not enough and we must add the repo here.
8
+ * Uses env MAPBOX_DOWNLOADS_TOKEN so EAS Build and local builds can supply the secret.
9
+ */
10
+ export const withMapboxNavSettingsGradle: ConfigPlugin = (config) => {
11
+ return withSettingsGradle(config, (config) => {
12
+ let contents = config.modResults.contents;
13
+
14
+ if (contents.includes("api.mapbox.com/downloads/v2/releases/maven")) {
15
+ return config;
16
+ }
17
+ if (
18
+ !contents.includes("dependencyResolutionManagement") ||
19
+ !contents.includes("repositories {")
20
+ ) {
21
+ return config;
22
+ }
23
+
24
+ const mapboxMaven = `
25
+ // @baeckerherz/expo-mapbox-navigation: Mapbox Navigation SDK Maven repository
26
+ maven {
27
+ url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
28
+ authentication {
29
+ basic(BasicAuthentication)
30
+ }
31
+ credentials {
32
+ username = "mapbox"
33
+ password = (System.getenv("MAPBOX_DOWNLOADS_TOKEN") ?: "")
34
+ }
35
+ }`;
36
+
37
+ contents = contents.replace(
38
+ /(repositories\s*\{)/,
39
+ `$1${mapboxMaven}`
40
+ );
41
+
42
+ config.modResults.contents = contents;
43
+ return config;
44
+ });
45
+ };