Skip to content

Commit 92ffdb7

Browse files
authored
feat: allow using google location engine on Android (#586)
1 parent 3a4c038 commit 92ffdb7

File tree

10 files changed

+273
-27
lines changed

10 files changed

+273
-27
lines changed

android/build.gradle

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
buildscript {
22
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
3-
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["MapLibreReactNative_kotlinVersion"]
3+
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["org.maplibre.reactnative.kotlinVersion"]
44

55
repositories {
66
google()
@@ -32,11 +32,15 @@ apply plugin: "kotlin-android"
3232
// }
3333

3434
def getExtOrDefault(name) {
35-
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["MapLibreReactNative_" + name]
35+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["org.maplibre.reactnative." + name]
36+
}
37+
38+
def getConfigurableExtOrDefault(name) {
39+
return rootProject.ext.has("org.maplibre.reactnative." + name) ? rootProject.ext.get("org.maplibre.reactnative." + name) : project.properties["org.maplibre.reactnative." + name]
3640
}
3741

3842
def getExtOrIntegerDefault(name) {
39-
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["MapLibreReactNative_" + name]).toInteger()
43+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["org.maplibre.reactnative." + name]).toInteger()
4044
}
4145

4246
def supportsNamespace() {
@@ -68,6 +72,20 @@ android {
6872
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
6973
}
7074

75+
sourceSets {
76+
main {
77+
java.srcDirs = ['src/main/java']
78+
79+
if (getConfigurableExtOrDefault("locationEngine") == "default") {
80+
java.srcDirs += 'src/main/location-engine-default'
81+
} else if (getConfigurableExtOrDefault("locationEngine") == "google") {
82+
java.srcDirs += 'src/main/location-engine-google'
83+
} else {
84+
throw new GradleException("org.maplibre.reactnative.locationEngine.locationEngine should be one of [\"default\", \"google\"]`. \"${getConfigurableExtOrDefault("locationEngine")}\" was provided.")
85+
}
86+
}
87+
}
88+
7189
buildTypes {
7290
release {
7391
minifyEnabled false
@@ -106,11 +124,16 @@ dependencies {
106124
implementation "androidx.vectordrawable:vectordrawable:1.1.0"
107125
implementation "androidx.annotation:annotation:1.7.0"
108126
implementation "androidx.appcompat:appcompat:1.6.1"
109-
implementation "com.squareup.okhttp3:okhttp:${getExtOrDefault('okhttpVersion')}"
110-
implementation "com.squareup.okhttp3:okhttp-urlconnection:${getExtOrDefault('okhttpVersion')}"
127+
implementation "com.squareup.okhttp3:okhttp:${getConfigurableExtOrDefault('okhttpVersion')}"
128+
implementation "com.squareup.okhttp3:okhttp-urlconnection:${getConfigurableExtOrDefault('okhttpVersion')}"
111129

112-
// MapLibre plugins
130+
// MapLibre Plugins
113131
implementation ("org.maplibre.gl:android-plugin-localization-v9:3.0.1")
114132
implementation ("org.maplibre.gl:android-plugin-annotation-v9:3.0.1")
115133
implementation ("org.maplibre.gl:android-plugin-markerview-v9:3.0.1")
134+
135+
// Dependencies for Google Location Engine
136+
if (getConfigurableExtOrDefault("locationEngine") == "google") {
137+
implementation "com.google.android.gms:play-services-location:21.3.0"
138+
}
116139
}

android/gradle.properties

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
MapLibreReactNative_kotlinVersion=1.7.0
2-
MapLibreReactNative_minSdkVersion=21
3-
MapLibreReactNative_targetSdkVersion=31
4-
MapLibreReactNative_compileSdkVersion=31
5-
MapLibreReactNative_ndkversion=21.4.7075529
1+
org.maplibre.reactnative.kotlinVersion=1.7.0
2+
org.maplibre.reactnative.minSdkVersion=21
3+
org.maplibre.reactnative.targetSdkVersion=31
4+
org.maplibre.reactnative.compileSdkVersion=31
5+
org.maplibre.reactnative.ndkVersion=21.4.7075529
66

7-
MapLibreReactNative_okhttpVersion=4.9.0
7+
# MapLibre React Native Customizations
8+
9+
org.maplibre.reactnative.okhttpVersion=4.9.0
10+
11+
# Available values: default, google
12+
org.maplibre.reactnative.locationEngine=default

android/src/main/java/org/maplibre/reactnative/location/LocationManager.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,14 @@
77

88
import org.maplibre.android.location.engine.LocationEngine;
99
import org.maplibre.android.location.engine.LocationEngineCallback;
10-
11-
/*
12-
import com.mapbox.android.core.location.LocationEngineListener;
13-
import com.mapbox.android.core.location.LocationEnginePriority;
14-
*/
15-
16-
import org.maplibre.android.location.engine.LocationEngineDefault;
1710
import org.maplibre.android.location.engine.LocationEngineRequest;
1811
import org.maplibre.android.location.engine.LocationEngineResult;
1912
import org.maplibre.android.location.permissions.PermissionsManager;
13+
import org.maplibre.reactnative.location.engine.LocationEngineProvider;
2014

2115
import java.lang.ref.WeakReference;
2216
import java.util.ArrayList;
2317
import java.util.List;
24-
import java.util.Locale;
2518

2619
@SuppressWarnings({"MissingPermission"})
2720
public class LocationManager implements LocationEngineCallback<LocationEngineResult> {
@@ -58,8 +51,10 @@ private LocationManager(Context context) {
5851
this.buildEngineRequest();
5952

6053
}
54+
6155
private void buildEngineRequest() {
62-
locationEngine = LocationEngineDefault.INSTANCE.getDefaultLocationEngine(this.context.getApplicationContext());
56+
locationEngine = new LocationEngineProvider().getLocationEngine(context);
57+
6358
locationEngineRequest = new LocationEngineRequest.Builder(DEFAULT_INTERVAL_MILLIS)
6459
.setFastestInterval(DEFAULT_FASTEST_INTERVAL_MILLIS)
6560
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
@@ -78,9 +73,11 @@ public void removeLocationListener(OnUserLocationChange listener) {
7873
listeners.remove(listener);
7974
}
8075
}
76+
8177
public void setMinDisplacement(float minDisplacement) {
8278
mMinDisplacement = minDisplacement;
8379
}
80+
8481
public void enable() {
8582
if (!PermissionsManager.areLocationPermissionsGranted(context)) {
8683
return;
@@ -134,8 +131,7 @@ public void getLastKnownLocation(LocationEngineCallback<LocationEngineResult> ca
134131

135132
try {
136133
locationEngine.getLastLocation(callback);
137-
}
138-
catch(Exception exception) {
134+
} catch (Exception exception) {
139135
Log.w(LOG_TAG, exception);
140136
callback.onFailure(exception);
141137
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.maplibre.reactnative.location.engine;
2+
3+
import android.content.Context;
4+
import android.util.Log;
5+
6+
import org.maplibre.android.location.engine.LocationEngine;
7+
import org.maplibre.android.location.engine.LocationEngineDefault;
8+
9+
public class DefaultLocationEngineProvider implements LocationEngineProvidable {
10+
private static final String LOG_TAG = "DefaultLocationEngineProvider";
11+
12+
@Override
13+
public LocationEngine getLocationEngine(Context context) {
14+
LocationEngine locationEngine = LocationEngineDefault.INSTANCE.getDefaultLocationEngine(context.getApplicationContext());
15+
Log.d(LOG_TAG, "DefaultLocationEngine will be used.");
16+
return locationEngine;
17+
}
18+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.maplibre.reactnative.location.engine;
2+
3+
import android.content.Context;
4+
5+
import org.maplibre.android.location.engine.LocationEngine;
6+
7+
public interface LocationEngineProvidable {
8+
LocationEngine getLocationEngine(Context context);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.maplibre.reactnative.location.engine;
2+
3+
import android.content.Context;
4+
5+
import org.maplibre.android.location.engine.LocationEngine;
6+
7+
public class LocationEngineProvider implements LocationEngineProvidable {
8+
@Override
9+
public LocationEngine getLocationEngine(Context context) {
10+
return new DefaultLocationEngineProvider().getLocationEngine(context);
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package org.maplibre.reactnative.location.engine;
2+
3+
import android.annotation.SuppressLint;
4+
import android.app.PendingIntent;
5+
import android.content.Context;
6+
import android.location.Location;
7+
import android.os.Looper;
8+
9+
import androidx.annotation.NonNull;
10+
import androidx.annotation.Nullable;
11+
import androidx.annotation.VisibleForTesting;
12+
13+
import com.google.android.gms.location.FusedLocationProviderClient;
14+
import com.google.android.gms.location.LocationCallback;
15+
import com.google.android.gms.location.LocationRequest;
16+
import com.google.android.gms.location.LocationResult;
17+
import com.google.android.gms.location.LocationServices;
18+
import com.google.android.gms.location.Priority;
19+
import com.google.android.gms.tasks.OnFailureListener;
20+
import com.google.android.gms.tasks.OnSuccessListener;
21+
22+
import org.maplibre.android.location.engine.LocationEngineCallback;
23+
import org.maplibre.android.location.engine.LocationEngineImpl;
24+
import org.maplibre.android.location.engine.LocationEngineRequest;
25+
import org.maplibre.android.location.engine.LocationEngineResult;
26+
27+
import java.util.Collections;
28+
import java.util.List;
29+
30+
/**
31+
* Wraps implementation of Fused Location Provider
32+
*/
33+
public class GoogleLocationEngineImpl implements LocationEngineImpl<LocationCallback> {
34+
private final FusedLocationProviderClient fusedLocationProviderClient;
35+
36+
@VisibleForTesting
37+
GoogleLocationEngineImpl(FusedLocationProviderClient fusedLocationProviderClient) {
38+
this.fusedLocationProviderClient = fusedLocationProviderClient;
39+
}
40+
41+
public GoogleLocationEngineImpl(@NonNull Context context) {
42+
this.fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
43+
}
44+
45+
@NonNull
46+
@Override
47+
public LocationCallback createListener(LocationEngineCallback<LocationEngineResult> callback) {
48+
return new GoogleLocationEngineCallbackTransport(callback);
49+
}
50+
51+
@SuppressLint("MissingPermission")
52+
@Override
53+
public void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback)
54+
throws SecurityException {
55+
GoogleLastLocationEngineCallbackTransport transport =
56+
new GoogleLastLocationEngineCallbackTransport(callback);
57+
fusedLocationProviderClient.getLastLocation().addOnSuccessListener(transport).addOnFailureListener(transport);
58+
}
59+
60+
@SuppressLint("MissingPermission")
61+
@Override
62+
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
63+
@NonNull LocationCallback listener,
64+
@Nullable Looper looper) throws SecurityException {
65+
fusedLocationProviderClient.requestLocationUpdates(toGMSLocationRequest(request), listener, looper);
66+
}
67+
68+
@SuppressLint("MissingPermission")
69+
@Override
70+
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
71+
@NonNull PendingIntent pendingIntent) throws SecurityException {
72+
fusedLocationProviderClient.requestLocationUpdates(toGMSLocationRequest(request), pendingIntent);
73+
}
74+
75+
@Override
76+
public void removeLocationUpdates(@NonNull LocationCallback listener) {
77+
if (listener != null) {
78+
fusedLocationProviderClient.removeLocationUpdates(listener);
79+
}
80+
}
81+
82+
@Override
83+
public void removeLocationUpdates(PendingIntent pendingIntent) {
84+
if (pendingIntent != null) {
85+
fusedLocationProviderClient.removeLocationUpdates(pendingIntent);
86+
}
87+
}
88+
89+
private static LocationRequest toGMSLocationRequest(LocationEngineRequest request) {
90+
LocationRequest.Builder builder = new LocationRequest.Builder(request.getInterval());
91+
builder.setMinUpdateIntervalMillis(request.getFastestInterval());
92+
builder.setMinUpdateDistanceMeters(request.getDisplacement());
93+
builder.setMaxUpdateDelayMillis(request.getMaxWaitTime());
94+
builder.setPriority(toGMSLocationPriority(request.getPriority()));
95+
return builder.build();
96+
}
97+
98+
private static int toGMSLocationPriority(int enginePriority) {
99+
switch (enginePriority) {
100+
case LocationEngineRequest.PRIORITY_HIGH_ACCURACY:
101+
return Priority.PRIORITY_HIGH_ACCURACY;
102+
case LocationEngineRequest.PRIORITY_BALANCED_POWER_ACCURACY:
103+
return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
104+
case LocationEngineRequest.PRIORITY_LOW_POWER:
105+
return Priority.PRIORITY_LOW_POWER;
106+
case LocationEngineRequest.PRIORITY_NO_POWER:
107+
default:
108+
return Priority.PRIORITY_PASSIVE;
109+
}
110+
}
111+
112+
private static final class GoogleLocationEngineCallbackTransport extends LocationCallback {
113+
private final LocationEngineCallback<LocationEngineResult> callback;
114+
115+
GoogleLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
116+
this.callback = callback;
117+
}
118+
119+
@Override
120+
public void onLocationResult(LocationResult locationResult) {
121+
super.onLocationResult(locationResult);
122+
List<Location> locations = locationResult.getLocations();
123+
if (!locations.isEmpty()) {
124+
callback.onSuccess(LocationEngineResult.create(locations));
125+
} else {
126+
callback.onFailure(new Exception("Unavailable location"));
127+
}
128+
}
129+
}
130+
131+
@VisibleForTesting
132+
static final class GoogleLastLocationEngineCallbackTransport
133+
implements OnSuccessListener<Location>, OnFailureListener {
134+
private final LocationEngineCallback<LocationEngineResult> callback;
135+
136+
GoogleLastLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
137+
this.callback = callback;
138+
}
139+
140+
@Override
141+
public void onSuccess(Location location) {
142+
callback.onSuccess(location != null ? LocationEngineResult.create(location) :
143+
LocationEngineResult.create(Collections.<Location>emptyList()));
144+
}
145+
146+
@Override
147+
public void onFailure(@NonNull Exception e) {
148+
callback.onFailure(e);
149+
}
150+
}
151+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.maplibre.reactnative.location.engine;
2+
3+
import android.content.Context;
4+
import android.util.Log;
5+
6+
import com.google.android.gms.common.ConnectionResult;
7+
import com.google.android.gms.common.GoogleApiAvailability;
8+
9+
import org.maplibre.android.location.engine.LocationEngine;
10+
import org.maplibre.android.location.engine.LocationEngineProxy;
11+
12+
public class GoogleLocationEngineProvider implements LocationEngineProvidable {
13+
private static final String LOG_TAG = "GoogleLocationEngineProvider";
14+
15+
public LocationEngine getLocationEngine(Context context) {
16+
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
17+
LocationEngine locationEngine = new LocationEngineProxy<>(new GoogleLocationEngineImpl(context.getApplicationContext()));
18+
Log.d(LOG_TAG, "GoogleLocationEngine will be used.");
19+
return locationEngine;
20+
} else {
21+
return new DefaultLocationEngineProvider().getLocationEngine(context);
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.maplibre.reactnative.location.engine;
2+
3+
import android.content.Context;
4+
5+
import org.maplibre.android.location.engine.LocationEngine;
6+
7+
public class LocationEngineProvider implements LocationEngineProvidable {
8+
@Override
9+
public LocationEngine getLocationEngine(Context context) {
10+
return new GoogleLocationEngineProvider().getLocationEngine(context);
11+
}
12+
}

packages/examples/src/examples/UserLocation/UserLocationForNavigation.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@ export function UserLocationForNavigation() {
6060
followPitch={60}
6161
pitch={0}
6262
onUserTrackingModeChange={(event) => {
63-
console.log("js userTrackingModeChange");
64-
console.log("js", event.type);
65-
console.log("js", JSON.stringify(event.nativeEvent));
66-
6763
if (
6864
navigationActive &&
6965
!event.nativeEvent.payload.followUserLocation

0 commit comments

Comments
 (0)