Skip to content

Commit 450a54e

Browse files
authored
Set LocationEngine properties in android (#510)
Hello, I am currently developing a bike navigation app for a client (RadroutenPlaner Bayern). After publishing the first version of the app, we noticed that the location accuracy on Android was significantly poor when switching to a Following Mode. The location puck updates very slowly and with a noticeable delay, it felt like it was only using coarse location. Upon investigating, I discovered that MapLibre Android uses the FusedLocationProvider by default, which only accepts a balanced location priority. Observing other major navigation apps like Komoot and Outdooractive via logcat, I found that they switch from the native fused provider to the GPS provider when the device enters Following Mode. To address this issue, I added the ability to manipulate the native LocationEngine and LocationEngineRequest in flutter_maplibre. I introduced a property to the MaplibreMap widget called locationEnginePlatforms. This property currently allows you to adjust settings like interval, displacement, and priority (only on Android). I don't have extensive experience with Java and Kotlin, so please let me know if there are any issues or if improvements are needed. Thanks for your feedback!
1 parent 8246a84 commit 450a54e

File tree

9 files changed

+329
-20
lines changed

9 files changed

+329
-20
lines changed

maplibre_gl/android/src/main/java/org/maplibre/maplibregl/Convert.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import android.content.Context;
88
import android.graphics.Point;
99
import android.util.DisplayMetrics;
10+
import android.util.Log;
11+
import org.maplibre.android.location.engine.LocationEngineRequest;
1012
import org.maplibre.geojson.Polygon;
1113
import org.maplibre.android.camera.CameraPosition;
1214
import org.maplibre.android.camera.CameraUpdate;
@@ -133,6 +135,17 @@ static LatLngBounds toLatLngBounds(Object o) {
133135
return builder.build();
134136
}
135137

138+
static LocationEngineRequest toLocationEngineRequest(Object o) {
139+
if (o == null) {
140+
return null;
141+
}
142+
List<?> data = toList(o);
143+
return new LocationEngineRequest.Builder(toInt(data.get(0)))
144+
.setPriority(toInt(data.get(1)))
145+
.setDisplacement(toInt(data.get(2)))
146+
.build();
147+
}
148+
136149
static List<LatLng> toLatLngList(Object o, boolean flippedOrder) {
137150
if (o == null) {
138151
return null;
@@ -208,6 +221,12 @@ static String toString(Object o) {
208221
static void interpretMapLibreMapOptions(Object o, MapLibreMapOptionsSink sink, Context context) {
209222
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
210223
final Map<?, ?> data = toMap(o);
224+
225+
final Object locationEngineProperties = data.get("locationEngineProperties");
226+
if (locationEngineProperties != null) {
227+
final List<?> locationEnginePropertiesList = toList(locationEngineProperties);
228+
sink.setLocationEngineProperties(toLocationEngineRequest(locationEnginePropertiesList));
229+
}
211230
final Object cameraTargetBounds = data.get("cameraTargetBounds");
212231
if (cameraTargetBounds != null) {
213232
final List<?> targetData = toList(cameraTargetBounds);
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package org.maplibre.maplibregl;
2+
3+
import android.annotation.SuppressLint;
4+
import android.app.PendingIntent;
5+
import android.content.Context;
6+
import android.location.Location;
7+
import android.location.LocationListener;
8+
import android.location.LocationManager;
9+
import android.os.Bundle;
10+
import android.os.Looper;
11+
import android.util.Log;
12+
13+
import androidx.annotation.NonNull;
14+
import androidx.annotation.Nullable;
15+
import androidx.annotation.VisibleForTesting;
16+
17+
import org.maplibre.android.location.engine.LocationEngineCallback;
18+
import org.maplibre.android.location.engine.LocationEngineRequest;
19+
import org.maplibre.android.location.engine.LocationEngineResult;
20+
import org.maplibre.android.location.engine.LocationEngineImpl;
21+
22+
23+
public class MapLibreGPSLocationEngine implements LocationEngineImpl<LocationListener> {
24+
private static final String TAG = "GPSLocationEngine";
25+
final LocationManager locationManager;
26+
27+
String currentProvider = LocationManager.PASSIVE_PROVIDER;
28+
29+
public MapLibreGPSLocationEngine(@NonNull Context context) {
30+
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
31+
}
32+
33+
@NonNull
34+
@Override
35+
public LocationListener createListener(LocationEngineCallback<LocationEngineResult> callback) {
36+
return new AndroidLocationEngineCallbackTransport(callback);
37+
}
38+
39+
@Override
40+
public void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback)
41+
throws SecurityException {
42+
Location lastLocation = getLastLocationFor(currentProvider);
43+
if (lastLocation != null) {
44+
callback.onSuccess(LocationEngineResult.create(lastLocation));
45+
return;
46+
}
47+
48+
for (String provider : locationManager.getAllProviders()) {
49+
lastLocation = getLastLocationFor(provider);
50+
if (lastLocation != null) {
51+
callback.onSuccess(LocationEngineResult.create(lastLocation));
52+
return;
53+
}
54+
}
55+
callback.onFailure(new Exception("Last location unavailable"));
56+
}
57+
58+
@SuppressLint("MissingPermission")
59+
Location getLastLocationFor(String provider) throws SecurityException {
60+
Location location = null;
61+
try {
62+
location = locationManager.getLastKnownLocation(provider);
63+
} catch (IllegalArgumentException iae) {
64+
Log.e(TAG, iae.toString());
65+
}
66+
return location;
67+
}
68+
69+
@SuppressLint("MissingPermission")
70+
@Override
71+
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
72+
@NonNull LocationListener listener,
73+
@Nullable Looper looper) throws SecurityException {
74+
currentProvider = getBestProvider(request.getPriority());
75+
locationManager.requestLocationUpdates(currentProvider, request.getInterval(), request.getDisplacement(),
76+
listener, looper);
77+
}
78+
79+
@SuppressLint("MissingPermission")
80+
@Override
81+
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
82+
@NonNull PendingIntent pendingIntent) throws SecurityException {
83+
currentProvider = getBestProvider(request.getPriority());
84+
locationManager.requestLocationUpdates(currentProvider, request.getInterval(),
85+
request.getDisplacement(), pendingIntent);
86+
}
87+
88+
@SuppressLint("MissingPermission")
89+
@Override
90+
public void removeLocationUpdates(@NonNull LocationListener listener) {
91+
if (listener != null) {
92+
locationManager.removeUpdates(listener);
93+
}
94+
}
95+
96+
@Override
97+
public void removeLocationUpdates(PendingIntent pendingIntent) {
98+
if (pendingIntent != null) {
99+
locationManager.removeUpdates(pendingIntent);
100+
}
101+
}
102+
103+
private String getBestProvider(int priority) {
104+
String provider = null;
105+
if (priority != LocationEngineRequest.PRIORITY_NO_POWER) {
106+
provider = LocationManager.GPS_PROVIDER;
107+
}
108+
return provider != null ? provider : LocationManager.PASSIVE_PROVIDER;
109+
}
110+
111+
112+
@VisibleForTesting
113+
static final class AndroidLocationEngineCallbackTransport implements LocationListener {
114+
private final LocationEngineCallback<LocationEngineResult> callback;
115+
116+
AndroidLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
117+
this.callback = callback;
118+
}
119+
120+
@Override
121+
public void onLocationChanged(Location location) {
122+
callback.onSuccess(LocationEngineResult.create(location));
123+
}
124+
125+
@Override
126+
public void onStatusChanged(String s, int i, Bundle bundle) {
127+
// noop
128+
}
129+
130+
@Override
131+
public void onProviderEnabled(String s) {
132+
// noop
133+
}
134+
135+
@Override
136+
public void onProviderDisabled(String s) {
137+
callback.onFailure(new Exception("Current provider disabled"));
138+
}
139+
}
140+
}

maplibre_gl/android/src/main/java/org/maplibre/maplibregl/MapLibreMapBuilder.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import androidx.annotation.NonNull;
1010
import org.maplibre.android.camera.CameraPosition;
1111
import org.maplibre.android.geometry.LatLngBounds;
12+
import org.maplibre.android.location.engine.LocationEngineRequest;
1213
import org.maplibre.android.maps.MapLibreMapOptions;
1314
import io.flutter.plugin.common.BinaryMessenger;
1415

@@ -23,6 +24,7 @@ class MapLibreMapBuilder implements MapLibreMapOptionsSink {
2324
private int myLocationRenderMode = 0;
2425
private String styleString = "";
2526
private LatLngBounds bounds = null;
27+
private LocationEngineRequest locationEngineRequest = null;
2628

2729
MapLibreMapController build(
2830
int id,
@@ -43,6 +45,10 @@ MapLibreMapController build(
4345
controller.setCameraTargetBounds(bounds);
4446
}
4547

48+
if(null != locationEngineRequest ){
49+
controller.setLocationEngineProperties(locationEngineRequest);
50+
}
51+
4652
return controller;
4753
}
4854

@@ -206,4 +212,9 @@ public void setAttributionButtonMargins(int x, int y) {
206212
public void setDragEnabled(boolean enabled) {
207213
this.dragEnabled = enabled;
208214
}
215+
216+
@Override
217+
public void setLocationEngineProperties(@NonNull LocationEngineRequest locationEngineRequest) {
218+
this.locationEngineRequest = locationEngineRequest;
219+
}
209220
}

maplibre_gl/android/src/main/java/org/maplibre/maplibregl/MapLibreMapController.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,13 @@
3232
import com.google.gson.JsonArray;
3333
import com.google.gson.JsonElement;
3434
import com.google.gson.JsonParser;
35+
3536
import org.maplibre.android.gestures.AndroidGesturesManager;
3637
import org.maplibre.android.gestures.MoveGestureDetector;
38+
import org.maplibre.android.location.engine.LocationEngine;
39+
import org.maplibre.android.location.engine.LocationEngineDefault;
40+
import org.maplibre.android.location.engine.LocationEngineProxy;
41+
import org.maplibre.android.location.engine.LocationEngineRequest;
3742
import org.maplibre.geojson.Feature;
3843
import org.maplibre.geojson.FeatureCollection;
3944
import org.maplibre.android.camera.CameraPosition;
@@ -1863,6 +1868,20 @@ public void setCameraTargetBounds(LatLngBounds bounds) {
18631868
this.bounds = bounds;
18641869
}
18651870

1871+
@Override
1872+
public void setLocationEngineProperties(LocationEngineRequest locationEngineRequest){
1873+
if(locationComponent != null){
1874+
if(locationEngineRequest.getPriority() == LocationEngineRequest.PRIORITY_HIGH_ACCURACY){
1875+
locationComponent.setLocationEngine(new LocationEngineProxy(
1876+
new MapLibreGPSLocationEngine(context)));
1877+
} else {
1878+
locationComponent.setLocationEngine(
1879+
LocationEngineDefault.INSTANCE.getDefaultLocationEngine(context));
1880+
}
1881+
locationComponent.setLocationEngineRequest(locationEngineRequest);
1882+
}
1883+
}
1884+
18661885
@Override
18671886
public void setCompassEnabled(boolean compassEnabled) {
18681887
mapLibreMap.getUiSettings().setCompassEnabled(compassEnabled);

maplibre_gl/android/src/main/java/org/maplibre/maplibregl/MapLibreMapOptionsSink.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package org.maplibre.maplibregl
55

66
import org.maplibre.android.geometry.LatLngBounds
7+
import org.maplibre.android.location.engine.LocationEngineRequest
78

89
/** Receiver of MapLibreMap configuration options. */
910
internal interface MapLibreMapOptionsSink {
@@ -42,4 +43,6 @@ internal interface MapLibreMapOptionsSink {
4243
fun setAttributionButtonGravity(gravity: Int)
4344

4445
fun setAttributionButtonMargins(x: Int, y: Int)
46+
47+
fun setLocationEngineProperties(locationEngineRequest: LocationEngineRequest)
4548
}

maplibre_gl/lib/maplibre_gl.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export 'package:maplibre_gl_platform_interface/maplibre_gl_platform_interface.da
6666
LatLngQuad,
6767
Line,
6868
LineOptions,
69+
LocationEngineAndroidProperties,
70+
LocationEnginePlatforms,
71+
LocationPriority,
6972
MapLibreMethodChannel,
7073
MapLibrePlatform,
7174
MinMaxZoomPreference,

maplibre_gl/lib/src/maplibre_map.dart

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class MapLibreMap extends StatefulWidget {
2020
this.styleString = MapLibreStyles.demo,
2121
this.onMapCreated,
2222
this.onStyleLoadedCallback,
23+
this.locationEnginePlatforms = LocationEnginePlatforms.defaultPlatform,
2324
this.gestureRecognizers,
2425
this.compassEnabled = true,
2526
this.cameraTargetBounds = CameraTargetBounds.unbounded,
@@ -67,6 +68,10 @@ class MapLibreMap extends StatefulWidget {
6768
assert(annotationOrder.length <= 4),
6869
assert(annotationConsumeTapEvents.length > 0);
6970

71+
/// The properties for the platform-specific location engine.
72+
/// Only has an impact if [myLocationEnabled] is set to true.
73+
final LocationEnginePlatforms locationEnginePlatforms;
74+
7075
/// Defines the layer order of annotations displayed on map
7176
///
7277
/// Any annotation type can only be contained once, so 0 to 4 types
@@ -338,29 +343,30 @@ class _MapLibreMapState extends State<MapLibreMap> {
338343
/// When used to change configuration, null values will be interpreted as
339344
/// "do not change this configuration option".
340345
class _MapLibreMapOptions {
341-
_MapLibreMapOptions({
342-
this.compassEnabled,
343-
this.cameraTargetBounds,
344-
this.styleString,
345-
this.minMaxZoomPreference,
346-
required this.rotateGesturesEnabled,
347-
required this.scrollGesturesEnabled,
348-
required this.tiltGesturesEnabled,
349-
required this.zoomGesturesEnabled,
350-
required this.doubleClickZoomEnabled,
351-
this.trackCameraPosition,
352-
this.myLocationEnabled,
353-
this.myLocationTrackingMode,
354-
this.myLocationRenderMode,
355-
this.logoViewMargins,
356-
this.compassViewPosition,
357-
this.compassViewMargins,
358-
this.attributionButtonPosition,
359-
this.attributionButtonMargins,
360-
});
346+
_MapLibreMapOptions(
347+
{this.compassEnabled,
348+
this.cameraTargetBounds,
349+
this.styleString,
350+
this.minMaxZoomPreference,
351+
required this.rotateGesturesEnabled,
352+
required this.scrollGesturesEnabled,
353+
required this.tiltGesturesEnabled,
354+
required this.zoomGesturesEnabled,
355+
required this.doubleClickZoomEnabled,
356+
this.trackCameraPosition,
357+
this.myLocationEnabled,
358+
this.myLocationTrackingMode,
359+
this.myLocationRenderMode,
360+
this.logoViewMargins,
361+
this.compassViewPosition,
362+
this.compassViewMargins,
363+
this.attributionButtonPosition,
364+
this.attributionButtonMargins,
365+
this.locationEnginePlatforms});
361366

362367
_MapLibreMapOptions.fromWidget(MapLibreMap map)
363368
: this(
369+
locationEnginePlatforms: map.locationEnginePlatforms,
364370
compassEnabled: map.compassEnabled,
365371
cameraTargetBounds: map.cameraTargetBounds,
366372
styleString: map.styleString,
@@ -418,6 +424,8 @@ class _MapLibreMapOptions {
418424

419425
final Point? attributionButtonMargins;
420426

427+
final LocationEnginePlatforms? locationEnginePlatforms;
428+
421429
final _gestureGroup = {
422430
'rotateGesturesEnabled',
423431
'scrollGesturesEnabled',
@@ -464,6 +472,7 @@ class _MapLibreMapOptions {
464472
addIfNonNull('attributionButtonPosition', attributionButtonPosition?.index);
465473
addIfNonNull(
466474
'attributionButtonMargins', pointToArray(attributionButtonMargins));
475+
addIfNonNull('locationEngineProperties', locationEnginePlatforms?.toList());
467476
return optionsMap;
468477
}
469478

maplibre_gl_platform_interface/lib/maplibre_gl_platform_interface.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
library maplibre_gl_platform_interface;
22

33
import 'dart:async';
4+
import 'dart:io';
45
import 'dart:convert';
56
import 'dart:math';
67
import 'package:flutter/foundation.dart';
@@ -20,3 +21,4 @@ part 'src/fill.dart';
2021
part 'src/ui.dart';
2122
part 'src/maplibre_gl_platform_interface.dart';
2223
part 'src/source_properties.dart';
24+
part 'src/location_engine_properties.dart';

0 commit comments

Comments
 (0)