From cb1aaf89664e974f3a522e93970d9bbb1668024a Mon Sep 17 00:00:00 2001 From: Sempakonka <47318358+Sempakonka@users.noreply.github.com> Date: Thu, 26 Nov 2020 14:33:57 +0100 Subject: [PATCH] Implemented requested changes as well as improved documentation. Added tests to increase code coverage to 100%. --- README.md | 2 +- geolocator/README.md | 2 +- .../android/src/main/AndroidManifest.xml | 2 - .../baseflow/geolocator/GeolocatorPlugin.java | 50 ++-- ...amImpl.java => NmeaStreamHandlerImpl.java} | 19 +- ...pl.java => PositionStreamHandlerImpl.java} | 4 +- .../location/GeolocationManager.java | 68 +----- .../GnssNmeaMessageClient.java | 10 +- .../GpsNmeaMessageClient.java | 10 +- .../NmeaChangedCallback.java | 2 +- .../geolocator/nmea/NmeaMessageManager.java | 41 ++++ .../NmeaMessageaClient.java | 2 +- .../permission/PermissionManager.java | 41 ++++ geolocator/lib/geolocator.dart | 11 +- geolocator/pubspec.yaml | 2 +- .../src/geolocator_platform_interface.dart | 38 ++- .../method_channel_geolocator.dart | 2 +- .../lib/src/models/nmea_message.dart | 9 +- .../geolocator_platform_interface_test.dart | 16 +- .../method_channel_geolocator_test.dart | 224 ++++++++++++++++++ .../test/src/models/nmea_test.dart | 95 ++++++++ 21 files changed, 519 insertions(+), 131 deletions(-) rename geolocator/android/src/main/java/com/baseflow/geolocator/{NmeaStreamImpl.java => NmeaStreamHandlerImpl.java} (80%) rename geolocator/android/src/main/java/com/baseflow/geolocator/{PositionStreamImpl.java => PositionStreamHandlerImpl.java} (95%) rename geolocator/android/src/main/java/com/baseflow/geolocator/{location => nmea}/GnssNmeaMessageClient.java (88%) rename geolocator/android/src/main/java/com/baseflow/geolocator/{location => nmea}/GpsNmeaMessageClient.java (87%) rename geolocator/android/src/main/java/com/baseflow/geolocator/{location => nmea}/NmeaChangedCallback.java (73%) create mode 100644 geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaMessageManager.java rename geolocator/android/src/main/java/com/baseflow/geolocator/{location => nmea}/NmeaMessageaClient.java (84%) create mode 100644 geolocator_platform_interface/test/src/models/nmea_test.dart diff --git a/README.md b/README.md index cc3e20cb..8eb45977 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ To use this plugin, add `geolocator` as a [dependency in your pubspec.yaml file] ```yaml dependencies: - geolocator: ^6.1.8 + geolocator: ^6.1.9 ```
diff --git a/geolocator/README.md b/geolocator/README.md index fac54ce9..8f79c110 100644 --- a/geolocator/README.md +++ b/geolocator/README.md @@ -23,7 +23,7 @@ To use this plugin, add `geolocator` as a [dependency in your pubspec.yaml file] ```yaml dependencies: - geolocator: ^6.1.8 + geolocator: ^6.1.9 ```
diff --git a/geolocator/android/src/main/AndroidManifest.xml b/geolocator/android/src/main/AndroidManifest.xml index 7b9cd8e8..4ac47fcd 100644 --- a/geolocator/android/src/main/AndroidManifest.xml +++ b/geolocator/android/src/main/AndroidManifest.xml @@ -1,5 +1,3 @@ - - diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/GeolocatorPlugin.java b/geolocator/android/src/main/java/com/baseflow/geolocator/GeolocatorPlugin.java index 9b1e70b0..1f6f67b7 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/GeolocatorPlugin.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/GeolocatorPlugin.java @@ -1,11 +1,10 @@ package com.baseflow.geolocator; -import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.baseflow.geolocator.location.GeolocationManager; +import com.baseflow.geolocator.nmea.NmeaMessageManager; import com.baseflow.geolocator.permission.PermissionManager; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; @@ -18,14 +17,15 @@ public class GeolocatorPlugin implements FlutterPlugin, ActivityAware { private static final String TAG = "GeocodingPlugin"; private final PermissionManager permissionManager; private final GeolocationManager geolocationManager; + private final NmeaMessageManager nmeaMessageManager; @Nullable private MethodCallHandlerImpl methodCallHandler; @Nullable - private PositionStreamImpl streamHandler; + private PositionStreamHandlerImpl positionStreamHandler; @Nullable - private NmeaStreamImpl nmeaStream; + private NmeaStreamHandlerImpl nmeaStreamHandler; @Nullable private Registrar pluginRegistrar; @@ -34,8 +34,8 @@ public class GeolocatorPlugin implements FlutterPlugin, ActivityAware { private ActivityPluginBinding pluginBinding; public GeolocatorPlugin() { - this.permissionManager = new PermissionManager(); + this.nmeaMessageManager = new NmeaMessageManager(permissionManager); this.geolocationManager = new GeolocationManager(permissionManager); } @@ -59,11 +59,11 @@ public static void registerWith(Registrar registrar) { methodCallHandler.startListening(registrar.context(), registrar.messenger()); methodCallHandler.setActivity(registrar.activity()); - PositionStreamImpl streamHandler = new PositionStreamImpl(geolocatorPlugin.geolocationManager); + PositionStreamHandlerImpl streamHandler = new PositionStreamHandlerImpl(geolocatorPlugin.geolocationManager); streamHandler.startListening(registrar.context(), registrar.messenger()); streamHandler.setActivity(registrar.activity()); - NmeaStreamImpl nmeaStream = new NmeaStreamImpl(geolocatorPlugin.geolocationManager); + NmeaStreamHandlerImpl nmeaStream = new NmeaStreamHandlerImpl(geolocatorPlugin.nmeaMessageManager); nmeaStream.startListening(registrar.context(), registrar.messenger()); nmeaStream.setActivity(registrar.activity()); } @@ -73,11 +73,11 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin methodCallHandler = new MethodCallHandlerImpl(this.permissionManager, this.geolocationManager); methodCallHandler.startListening( flutterPluginBinding.getApplicationContext(), flutterPluginBinding.getBinaryMessenger()); - streamHandler = new PositionStreamImpl(this.geolocationManager); - streamHandler.startListening( + positionStreamHandler = new PositionStreamHandlerImpl(this.geolocationManager); + positionStreamHandler.startListening( flutterPluginBinding.getApplicationContext(), flutterPluginBinding.getBinaryMessenger()); - nmeaStream = new NmeaStreamImpl(this.geolocationManager); - nmeaStream.startListening( + nmeaStreamHandler = new NmeaStreamHandlerImpl(this.nmeaMessageManager); + nmeaStreamHandler.startListening( flutterPluginBinding.getApplicationContext(), flutterPluginBinding.getBinaryMessenger()); } @@ -88,14 +88,14 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { methodCallHandler = null; } - if (streamHandler != null) { - streamHandler.stopListening(); - streamHandler = null; + if (positionStreamHandler != null) { + positionStreamHandler.stopListening(); + positionStreamHandler = null; } - if (nmeaStream != null) { - nmeaStream.stopListening(); - nmeaStream = null; + if (nmeaStreamHandler != null) { + nmeaStreamHandler.stopListening(); + nmeaStreamHandler = null; } } @@ -104,12 +104,12 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { if (methodCallHandler != null) { methodCallHandler.setActivity(binding.getActivity()); } - if (streamHandler != null) { - streamHandler.setActivity(binding.getActivity()); + if (positionStreamHandler != null) { + positionStreamHandler.setActivity(binding.getActivity()); } - if (nmeaStream != null) { - nmeaStream.setActivity(binding.getActivity()); + if (nmeaStreamHandler != null) { + nmeaStreamHandler.setActivity(binding.getActivity()); } this.pluginBinding = binding; @@ -131,12 +131,12 @@ public void onDetachedFromActivity() { if (methodCallHandler != null) { methodCallHandler.setActivity(null); } - if (streamHandler != null) { - streamHandler.setActivity(null); + if (positionStreamHandler != null) { + positionStreamHandler.setActivity(null); } - if (nmeaStream != null) { - nmeaStream.setActivity(null); + if (nmeaStreamHandler != null) { + nmeaStreamHandler.setActivity(null); } deregisterListeners(); diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/NmeaStreamImpl.java b/geolocator/android/src/main/java/com/baseflow/geolocator/NmeaStreamHandlerImpl.java similarity index 80% rename from geolocator/android/src/main/java/com/baseflow/geolocator/NmeaStreamImpl.java rename to geolocator/android/src/main/java/com/baseflow/geolocator/NmeaStreamHandlerImpl.java index 91fb0d49..e758ccc9 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/NmeaStreamImpl.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/NmeaStreamHandlerImpl.java @@ -6,17 +6,18 @@ import androidx.annotation.Nullable; import com.baseflow.geolocator.errors.ErrorCodes; import com.baseflow.geolocator.location.GeolocationManager; -import com.baseflow.geolocator.location.NmeaMessageaClient; +import com.baseflow.geolocator.nmea.NmeaMessageManager; +import com.baseflow.geolocator.nmea.NmeaMessageaClient; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; import java.util.HashMap; import java.util.Map; -class NmeaStreamImpl implements EventChannel.StreamHandler { +class NmeaStreamHandlerImpl implements EventChannel.StreamHandler { private static final String TAG = "NmeaStreamImpl"; - private final GeolocationManager geolocationManager; + private final NmeaMessageManager nmeaMessageManager; @Nullable private EventChannel channel; @@ -27,11 +28,11 @@ class NmeaStreamImpl implements EventChannel.StreamHandler { @Nullable private NmeaMessageaClient nmeaMessageaClient; - public NmeaStreamImpl(GeolocationManager geolocationManager) { - this.geolocationManager = geolocationManager; + public NmeaStreamHandlerImpl(NmeaMessageManager nmeaMessageManager) { + this.nmeaMessageManager = nmeaMessageManager; } - public static Map toMap(String n, Long l) { + private static Map toMap(String n, Long l) { if (n == null || l == null) { return null; } @@ -84,9 +85,9 @@ void stopListening() { @Override public void onListen(Object arguments, EventChannel.EventSink events) { - this.nmeaMessageaClient = geolocationManager.createNmeaClient(context); + this.nmeaMessageaClient = nmeaMessageManager.createNmeaClient(context); - geolocationManager.startNmeaUpdates( + nmeaMessageManager.startNmeaUpdates( context, activity, this.nmeaMessageaClient, @@ -98,7 +99,7 @@ public void onListen(Object arguments, EventChannel.EventSink events) { @Override public void onCancel(Object arguments) { if (this.nmeaMessageaClient != null) { - geolocationManager.stopNmeaUpdates(this.nmeaMessageaClient); + nmeaMessageManager.stopNmeaUpdates(this.nmeaMessageaClient); } } diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/PositionStreamImpl.java b/geolocator/android/src/main/java/com/baseflow/geolocator/PositionStreamHandlerImpl.java similarity index 95% rename from geolocator/android/src/main/java/com/baseflow/geolocator/PositionStreamImpl.java rename to geolocator/android/src/main/java/com/baseflow/geolocator/PositionStreamHandlerImpl.java index 3f05f3b9..c763bdc6 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/PositionStreamImpl.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/PositionStreamHandlerImpl.java @@ -15,7 +15,7 @@ import java.util.Map; -class PositionStreamImpl implements EventChannel.StreamHandler { +class PositionStreamHandlerImpl implements EventChannel.StreamHandler { private static final String TAG = "PositionStreamImpl"; @@ -26,7 +26,7 @@ class PositionStreamImpl implements EventChannel.StreamHandler { @Nullable private Activity activity; @Nullable private LocationClient locationClient; - public PositionStreamImpl(GeolocationManager geolocationManager) { + public PositionStreamHandlerImpl(GeolocationManager geolocationManager) { this.geolocationManager = geolocationManager; } diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java b/geolocator/android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java index 14648f48..4ef8f170 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java @@ -3,14 +3,10 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.os.Build.VERSION_CODES; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.baseflow.geolocator.errors.ErrorCallback; import com.baseflow.geolocator.errors.ErrorCodes; -import com.baseflow.geolocator.errors.PermissionUndefinedException; -import com.baseflow.geolocator.permission.LocationPermission; import com.baseflow.geolocator.permission.PermissionManager; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; @@ -37,7 +33,7 @@ public void getLastKnownPosition( PositionChangedCallback positionChangedCallback, ErrorCallback errorCallback) { - handlePermissions( + permissionManager.handlePermissions( context, activity, () -> { @@ -66,7 +62,7 @@ public void startPositionUpdates( this.locationClients.add(locationClient); - handlePermissions( + permissionManager.handlePermissions( context, activity, () -> locationClient.startPositionUpdates(activity, positionChangedCallback, errorCallback), @@ -78,26 +74,6 @@ public void stopPositionUpdates(@NonNull LocationClient locationClient) { locationClient.stopPositionUpdates(); } - - public void startNmeaUpdates(Context context, Activity activity, NmeaMessageaClient client, - NmeaChangedCallback nmeaChangedCallback, ErrorCallback errorCallback) { - - handlePermissions( - context, - activity, - () -> client.startNmeaUpdates(nmeaChangedCallback, errorCallback), - errorCallback); - } - - public void stopNmeaUpdates(NmeaMessageaClient client) { - client.stopNmeaUpdates(); - } - - public NmeaMessageaClient createNmeaClient(Context context) { - return android.os.Build.VERSION.SDK_INT >= VERSION_CODES.N ? new GnssNmeaMessageClient(context) - : new GpsNmeaMessageClient(context); - } - public LocationClient createLocationClient( Context context, boolean forceAndroidLocationManager, @@ -117,46 +93,6 @@ private boolean isGooglePlayServicesAvailable(Context context) { return resultCode == ConnectionResult.SUCCESS; } - private void handlePermissions( - Context context, - @Nullable Activity activity, - Runnable hasPermissionCallback, - ErrorCallback errorCallback) { - try { - LocationPermission permissionStatus = - permissionManager.checkPermissionStatus(context, activity); - - if (permissionStatus == LocationPermission.deniedForever) { - errorCallback.onError(ErrorCodes.permissionDenied); - return; - } - - if (permissionStatus == LocationPermission.whileInUse - || permissionStatus == LocationPermission.always) { - hasPermissionCallback.run(); - return; - } - - if (permissionStatus == LocationPermission.denied && activity != null) { - permissionManager.requestPermission( - activity, - (permission) -> { - if (permission == LocationPermission.whileInUse - || permission == LocationPermission.always) { - hasPermissionCallback.run(); - } else { - errorCallback.onError(ErrorCodes.permissionDenied); - } - }, - errorCallback); - } else { - errorCallback.onError(ErrorCodes.permissionDenied); - } - } catch (PermissionUndefinedException ex) { - errorCallback.onError(ErrorCodes.permissionDefinitionsNotFound); - } - } - @Override public boolean onActivityResult(int requestCode, int resultCode, Intent data) { for (LocationClient client : this.locationClients) { diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/location/GnssNmeaMessageClient.java b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/GnssNmeaMessageClient.java similarity index 88% rename from geolocator/android/src/main/java/com/baseflow/geolocator/location/GnssNmeaMessageClient.java rename to geolocator/android/src/main/java/com/baseflow/geolocator/nmea/GnssNmeaMessageClient.java index 48e391ab..32d95505 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/location/GnssNmeaMessageClient.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/GnssNmeaMessageClient.java @@ -1,4 +1,4 @@ -package com.baseflow.geolocator.location; +package com.baseflow.geolocator.nmea; import android.annotation.SuppressLint; import android.content.Context; @@ -32,17 +32,19 @@ public GnssNmeaMessageClient( this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } + @SuppressLint("MissingPermission") public void startNmeaUpdates(NmeaChangedCallback nmeaChangedCallback, ErrorCallback errorCallback) { this.locationManager.addNmeaListener(this, null); - this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 0, this, + this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 0, this, Looper.getMainLooper()); this.nmeaChangedCallback = nmeaChangedCallback; this.errorCallback = errorCallback; this.isListening = true; } + @SuppressLint("MissingPermission") @Override public void stopNmeaUpdates() { this.isListening = false; @@ -60,6 +62,7 @@ public void onNmeaMessage(String s, long l) { @Override public void onLocationChanged(@NonNull Location location) { + System.out.println("location changed"); } @@ -67,9 +70,10 @@ public void onLocationChanged(@NonNull Location location) { public void onProviderEnabled(String s) { } + @SuppressLint("MissingPermission") @Override public void onProviderDisabled(String s) { - if (s.equals(locationManager.GPS_PROVIDER)) { + if (s.equals(LocationManager.GPS_PROVIDER)) { if (isListening) { this.locationManager.removeUpdates(this); } diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/location/GpsNmeaMessageClient.java b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/GpsNmeaMessageClient.java similarity index 87% rename from geolocator/android/src/main/java/com/baseflow/geolocator/location/GpsNmeaMessageClient.java rename to geolocator/android/src/main/java/com/baseflow/geolocator/nmea/GpsNmeaMessageClient.java index 9b161c11..82864bf0 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/location/GpsNmeaMessageClient.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/GpsNmeaMessageClient.java @@ -1,5 +1,6 @@ -package com.baseflow.geolocator.location; +package com.baseflow.geolocator.nmea; +import android.annotation.SuppressLint; import android.content.Context; import android.location.GpsStatus; import android.location.Location; @@ -29,16 +30,18 @@ public GpsNmeaMessageClient( this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } + @SuppressLint("MissingPermission") public void startNmeaUpdates(NmeaChangedCallback nmeaChangedCallback, ErrorCallback errorCallback) { - locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 10000f, this); + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 0, this); locationManager.addNmeaListener(this); this.nmeaChangedCallback = nmeaChangedCallback; this.errorCallback = errorCallback; this.isListening = true; } + @SuppressLint("MissingPermission") @Override public void stopNmeaUpdates() { this.isListening = false; @@ -53,9 +56,10 @@ public void onLocationChanged(@NonNull Location location) { public void onProviderEnabled(String s) { } + @SuppressLint("MissingPermission") @Override public void onProviderDisabled(String s) { - if (s.equals(locationManager.GPS_PROVIDER)) { + if (s.equals(LocationManager.GPS_PROVIDER)) { if (isListening) { this.locationManager.removeUpdates(this); } diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/location/NmeaChangedCallback.java b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaChangedCallback.java similarity index 73% rename from geolocator/android/src/main/java/com/baseflow/geolocator/location/NmeaChangedCallback.java rename to geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaChangedCallback.java index 378a9e47..a65bcc86 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/location/NmeaChangedCallback.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaChangedCallback.java @@ -1,4 +1,4 @@ -package com.baseflow.geolocator.location; +package com.baseflow.geolocator.nmea; @FunctionalInterface diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaMessageManager.java b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaMessageManager.java new file mode 100644 index 00000000..d2c6e5b0 --- /dev/null +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaMessageManager.java @@ -0,0 +1,41 @@ +package com.baseflow.geolocator.nmea; + + +import android.app.Activity; +import android.content.Context; +import android.os.Build.VERSION_CODES; +import androidx.annotation.NonNull; +import com.baseflow.geolocator.errors.ErrorCallback; +import com.baseflow.geolocator.permission.PermissionManager; + +public class NmeaMessageManager { + + @NonNull + private final PermissionManager permissionManager; + + + public NmeaMessageManager(@NonNull PermissionManager permissionManager) { + this.permissionManager = permissionManager; + } + + + public void startNmeaUpdates(Context context, Activity activity, NmeaMessageaClient client, + NmeaChangedCallback nmeaChangedCallback, ErrorCallback errorCallback) { + + permissionManager.handlePermissions( + context, + activity, + () -> client.startNmeaUpdates(nmeaChangedCallback, errorCallback), + errorCallback); + } + + public void stopNmeaUpdates(NmeaMessageaClient client) { + client.stopNmeaUpdates(); + } + + public NmeaMessageaClient createNmeaClient(Context context) { + return android.os.Build.VERSION.SDK_INT >= VERSION_CODES.N ? new GnssNmeaMessageClient(context) + : new GpsNmeaMessageClient(context); + } + +} diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/location/NmeaMessageaClient.java b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaMessageaClient.java similarity index 84% rename from geolocator/android/src/main/java/com/baseflow/geolocator/location/NmeaMessageaClient.java rename to geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaMessageaClient.java index fb671064..21b48ae6 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/location/NmeaMessageaClient.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/nmea/NmeaMessageaClient.java @@ -1,4 +1,4 @@ -package com.baseflow.geolocator.location; +package com.baseflow.geolocator.nmea; import com.baseflow.geolocator.errors.ErrorCallback; diff --git a/geolocator/android/src/main/java/com/baseflow/geolocator/permission/PermissionManager.java b/geolocator/android/src/main/java/com/baseflow/geolocator/permission/PermissionManager.java index d41aa55f..d23a2577 100644 --- a/geolocator/android/src/main/java/com/baseflow/geolocator/permission/PermissionManager.java +++ b/geolocator/android/src/main/java/com/baseflow/geolocator/permission/PermissionManager.java @@ -27,6 +27,47 @@ public class PermissionManager implements PluginRegistry.RequestPermissionsResul @Nullable private ErrorCallback errorCallback; @Nullable private PermissionResultCallback resultCallback; + + public void handlePermissions( + Context context, + @Nullable Activity activity, + Runnable hasPermissionCallback, + ErrorCallback errorCallback) { + try { + LocationPermission permissionStatus = + checkPermissionStatus(context, activity); + + if (permissionStatus == LocationPermission.deniedForever) { + errorCallback.onError(ErrorCodes.permissionDenied); + return; + } + + if (permissionStatus == LocationPermission.whileInUse + || permissionStatus == LocationPermission.always) { + hasPermissionCallback.run(); + return; + } + + if (permissionStatus == LocationPermission.denied && activity != null) { + requestPermission( + activity, + (permission) -> { + if (permission == LocationPermission.whileInUse + || permission == LocationPermission.always) { + hasPermissionCallback.run(); + } else { + errorCallback.onError(ErrorCodes.permissionDenied); + } + }, + errorCallback); + } else { + errorCallback.onError(ErrorCodes.permissionDenied); + } + } catch (PermissionUndefinedException ex) { + errorCallback.onError(ErrorCodes.permissionDefinitionsNotFound); + } + } + public LocationPermission checkPermissionStatus(Context context, Activity activity) throws PermissionUndefinedException { String permission = determineFineOrCoarse(context); diff --git a/geolocator/lib/geolocator.dart b/geolocator/lib/geolocator.dart index 946c4b71..4ffb5ca3 100644 --- a/geolocator/lib/geolocator.dart +++ b/geolocator/lib/geolocator.dart @@ -261,14 +261,16 @@ class Geolocator { timeLimit: timeLimit, ); - /// Fires every time NMEA-0183 sentences are received. + /// Returns a stream emitting NMEA-0183 sentences when they are received from + /// the GNSS engine. With devices running a Android API level lower than 24 + /// NMEA-0183 sentences are received from the GPS engine. /// /// This event starts all location sensors on the device and will keep them /// active until you cancel listening to the stream or when the application /// is killed. /// /// ``` - /// StreamSubscription nmeaStream = getNmeaStream() + /// StreamSubscription nmeaStream = Geolocator.getNmeaStream() /// .listen((NmeaMessage nmea) { /// // Handle NMEA changes /// }); @@ -286,7 +288,7 @@ class Geolocator { static Stream getNmeaMessageStream() => GeolocatorPlatform.instance.getNmeaMessageStream(); - /// Fires whenever the location changes inside the bounds of the + /// Returns a stream emitting the location changes inside the bounds of the /// [desiredAccuracy]. /// /// This event starts all location sensors on the device and will keep them @@ -294,7 +296,8 @@ class Geolocator { /// is killed. /// /// ``` - /// StreamSubscription positionStream = getPositionStream() + /// StreamSubscription positionStream = + /// Geolocator.getPositionStream() /// .listen((Position position) { /// // Handle position changes /// }); diff --git a/geolocator/pubspec.yaml b/geolocator/pubspec.yaml index c0904263..2a9d470d 100644 --- a/geolocator/pubspec.yaml +++ b/geolocator/pubspec.yaml @@ -1,6 +1,6 @@ name: geolocator description: Geolocation plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API for generic location (GPS etc.) functions. -version: 6.1.8+1 +version: 6.1.9 homepage: https://github.com/Baseflow/flutter-geolocator/tree/master/geolocator publish_to: none diff --git a/geolocator_platform_interface/lib/src/geolocator_platform_interface.dart b/geolocator_platform_interface/lib/src/geolocator_platform_interface.dart index e5acdd53..9480982e 100644 --- a/geolocator_platform_interface/lib/src/geolocator_platform_interface.dart +++ b/geolocator_platform_interface/lib/src/geolocator_platform_interface.dart @@ -108,7 +108,36 @@ abstract class GeolocatorPlatform extends PlatformInterface { throw UnimplementedError('getCurrentPosition() has not been implemented.'); } - /// Fires whenever the location changes inside the bounds of the + /// Returns a stream emitting NMEA-0183 sentences when they are received from + /// the GNSS engine. With devices running a Android API level lower than 24 + /// NMEA-0183 sentences are received from the GPS engine. + /// + /// This event starts all location sensors on the device and will keep them + /// active until you cancel listening to the stream or when the application + /// is killed. + /// + /// ``` + /// StreamSubscription nmeaStream = Geolocator.getNmeaStream() + /// .listen((NmeaMessage nmea) { + /// // Handle NMEA changes + /// }); + /// ``` + /// + /// When no longer needed cancel the subscription + /// nmeaStream.cancel(); + /// + /// Throws a [PermissionDeniedException] when trying to request the device's + /// location when the user denied access. + /// Throws a [LocationServiceDisabledException] when the user allowed access, + /// but the location services of the device are disabled. + /// + /// for more info about NMEA 0183 see https://en.wikipedia.org/wiki/NMEA_0183 + Stream getNmeaMessageStream() { + throw UnimplementedError( + 'getNmeaMessageStream() has not been implemented.'); + } + + /// Returns a stream emitting the location changes inside the bounds of the /// [desiredAccuracy]. /// /// This event starts all location sensors on the device and will keep them @@ -116,7 +145,8 @@ abstract class GeolocatorPlatform extends PlatformInterface { /// is killed. /// /// ``` - /// StreamSubscription positionStream = getPositionStream() + /// StreamSubscription positionStream = + /// Geolocator.getPositionStream() /// .listen((Position position) { /// // Handle position changes /// }); @@ -152,10 +182,6 @@ abstract class GeolocatorPlatform extends PlatformInterface { throw UnimplementedError('getPositionStream() has not been implemented.'); } - Stream getNmeaMessageStream() { - throw UnimplementedError('requestPermission() has not been implemented.'); - } - /// Opens the App settings page. /// /// Returns [true] if the app settings page could be opened, otherwise diff --git a/geolocator_platform_interface/lib/src/implementations/method_channel_geolocator.dart b/geolocator_platform_interface/lib/src/implementations/method_channel_geolocator.dart index fd67ddca..fd3b394e 100644 --- a/geolocator_platform_interface/lib/src/implementations/method_channel_geolocator.dart +++ b/geolocator_platform_interface/lib/src/implementations/method_channel_geolocator.dart @@ -190,7 +190,7 @@ class MethodChannelGeolocator extends GeolocatorPlatform { return _nmeaMessageStream; } - var nmeaStream = nmeaChannel.receiveBroadcastStream(); + final nmeaStream = nmeaChannel.receiveBroadcastStream(); _nmeaMessageStream = nmeaStream .map((dynamic element) => diff --git a/geolocator_platform_interface/lib/src/models/nmea_message.dart b/geolocator_platform_interface/lib/src/models/nmea_message.dart index db526911..8c5e7af6 100644 --- a/geolocator_platform_interface/lib/src/models/nmea_message.dart +++ b/geolocator_platform_interface/lib/src/models/nmea_message.dart @@ -2,15 +2,16 @@ import 'package:meta/meta.dart'; @immutable -///nmea message +/// Contains all the NMEA information. class NmeaMessage { - ///nmea message + /// Constructs a NMEA message instance with the given values. NmeaMessage(this.message, this.timestamp); - ///message + /// The full NMEA-0183 message, as reported by the GNSS chipset. final String message; - ///timestamp + /// Date and time of the location fix, as reported by the GNSS chipset. + /// The value is specified in milliseconds since 0:00 UTC 1 January 1970. final DateTime timestamp; /// Converts the supplied [Map] to an instance of the [NmeaMessage] class. diff --git a/geolocator_platform_interface/test/geolocator_platform_interface_test.dart b/geolocator_platform_interface/test/geolocator_platform_interface_test.dart index 0fb2a095..bb80b150 100644 --- a/geolocator_platform_interface/test/geolocator_platform_interface_test.dart +++ b/geolocator_platform_interface/test/geolocator_platform_interface_test.dart @@ -114,6 +114,20 @@ void main() { ); }); + test( + // ignore: lines_longer_than_80_chars + 'Default implementation of getNmeaMessageStream should throw unimplemented error', + () { + // Arrange + final geolocatorPlatform = ExtendsGeolocatorPlatform(); + + // Act & Assert + expect( + geolocatorPlatform.getNmeaMessageStream, + throwsUnimplementedError, + ); + }); + test( // ignore: lines_longer_than_80_chars 'Default implementation of openAppSettings should throw unimplemented error', @@ -206,7 +220,7 @@ void main() { expect(bearing, 0.0); }); - test('the North pole to the Sounth pole bearing should be 180', () async { + test('the North pole to the South pole bearing should be 180', () async { final startLatitude = 90.0; final startLongitude = 0.0; final endLatitude = -90.0; diff --git a/geolocator_platform_interface/test/src/implementations/method_channel_geolocator_test.dart b/geolocator_platform_interface/test/src/implementations/method_channel_geolocator_test.dart index 83ded6d1..79426eb3 100644 --- a/geolocator_platform_interface/test/src/implementations/method_channel_geolocator_test.dart +++ b/geolocator_platform_interface/test/src/implementations/method_channel_geolocator_test.dart @@ -23,6 +23,13 @@ Position get mockPosition => Position( speedAccuracy: 0.0, isMocked: false); +NmeaMessage get mockNmeaMessage => NmeaMessage( + "GPGGA,170834,4124.8963,N,08151.6838,W,1,05,1.5,280.2,M,-34.0,M,,,*75", + DateTime.fromMillisecondsSinceEpoch( + 500, + isUtc: true, + )); + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -758,6 +765,223 @@ void main() { }); }); + group('getNmeaMessageStream: When requesting a stream of NMEA updates', () { + group('And requesting for NMEA update multiple times', () { + test('Should return the same stream', () { + final methodChannelGeolocator = MethodChannelGeolocator(); + final firstStream = methodChannelGeolocator.getNmeaMessageStream(); + final secondStream = methodChannelGeolocator.getNmeaMessageStream(); + + expect( + identical(firstStream, secondStream), + true, + ); + }); + }); + + test( + // ignore: lines_longer_than_80_chars + 'Should receive a stream with NMEA updates if permissions are granted', + () async { + // Arrange + final streamController = + StreamController>.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/nmea_updates', + stream: streamController.stream, + ); + + // Act + final nmeaStream = MethodChannelGeolocator().getNmeaMessageStream(); + final streamQueue = StreamQueue(nmeaStream); + + // Emit test events + streamController.add(mockNmeaMessage.toJson()); + streamController.add(mockNmeaMessage.toJson()); + streamController.add(mockNmeaMessage.toJson()); + + // Assert + expect(await streamQueue.next, mockNmeaMessage); + expect(await streamQueue.next, mockNmeaMessage); + expect(await streamQueue.next, mockNmeaMessage); + + // Clean up + await streamQueue.cancel(); + await streamController.close(); + }); + + test( + // ignore: lines_longer_than_80_chars + 'Should receive a permission denied exception if permission is denied', + () async { + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/nmea_updates', + stream: streamController.stream, + ); + + // Act + final nmeaStream = MethodChannelGeolocator().getNmeaMessageStream(); + final streamQueue = StreamQueue(nmeaStream); + + // Emit test error + streamController.addError(PlatformException( + code: 'PERMISSION_DENIED', + message: 'Permission denied', + details: null)); + + // Assert + expect( + streamQueue.next, + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Permission denied', + ), + )); + + // Clean up + streamQueue.cancel(); + streamController.close(); + }); + + test( + // ignore: lines_longer_than_80_chars + 'Should receive a location service disabled exception if location service is disabled', + () async { + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/nmea_updates', + stream: streamController.stream, + ); + + // Act + final nmeaStream = MethodChannelGeolocator().getNmeaMessageStream(); + final streamQueue = StreamQueue(nmeaStream); + + // Emit test error + streamController.addError(PlatformException( + code: 'LOCATION_SERVICES_DISABLED', + message: 'Location services disabled', + details: null)); + + // Assert + expect( + streamQueue.next, + throwsA( + isA(), + )); + + // Clean up + streamQueue.cancel(); + streamController.close(); + }); + + test( + // ignore: lines_longer_than_80_chars + 'Should receive a already subscribed exception', () async { + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/nmea_updates', + stream: streamController.stream, + ); + + // Act + final nmeaStream = MethodChannelGeolocator().getNmeaMessageStream(); + final streamQueue = StreamQueue(nmeaStream); + + // Emit test error + streamController.addError(PlatformException( + code: 'PERMISSION_REQUEST_IN_PROGRESS', + message: 'A permission request is already in progress', + details: null)); + + // Assert + expect( + streamQueue.next, + throwsA( + isA(), + )); + + // Clean up + streamQueue.cancel(); + streamController.close(); + }); + + test( + // ignore: lines_longer_than_80_chars + 'Should receive a already subscribed exception', () async { + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/nmea_updates', + stream: streamController.stream, + ); + + // Act + final nmeaStream = MethodChannelGeolocator().getNmeaMessageStream(); + final streamQueue = StreamQueue(nmeaStream); + + // Emit test error + streamController.addError(PlatformException( + code: 'LOCATION_SUBSCRIPTION_ACTIVE', + message: 'Already subscribed to receive a position stream', + details: null)); + + // Assert + expect( + streamQueue.next, + throwsA( + isA(), + )); + + // Clean up + streamQueue.cancel(); + streamController.close(); + }); + + test( + // ignore: lines_longer_than_80_chars + 'Should receive a position update exception', () async { + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/nmea_updates', + stream: streamController.stream, + ); + + // Act + final nmeaStream = MethodChannelGeolocator().getNmeaMessageStream(); + final streamQueue = StreamQueue(nmeaStream); + + // Emit test error + streamController.addError(PlatformException( + code: 'LOCATION_UPDATE_FAILURE', + message: 'A permission request is already in progress', + details: null)); + + // Assert + expect( + streamQueue.next, + throwsA( + isA(), + )); + + // Clean up + streamQueue.cancel(); + streamController.close(); + }); + }); + group('openAppSettings: When opening the App settings', () { test('Should receive true if the page can be opened', () async { // Arrange diff --git a/geolocator_platform_interface/test/src/models/nmea_test.dart b/geolocator_platform_interface/test/src/models/nmea_test.dart new file mode 100644 index 00000000..465bdc9b --- /dev/null +++ b/geolocator_platform_interface/test/src/models/nmea_test.dart @@ -0,0 +1,95 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:geolocator_platform_interface/geolocator_platform_interface.dart'; + +void main() { + group('hashCode tests:', () { + test('hashCode should be the same for two instances with the same values', + () { + // Arrange + final firstNmeaMessage = + NmeaMessage("message", DateTime.fromMicrosecondsSinceEpoch(500)); + final secondNmeaMessage = + NmeaMessage("message", DateTime.fromMicrosecondsSinceEpoch(500)); + + // Act & Assert + expect( + firstNmeaMessage.hashCode, + secondNmeaMessage.hashCode, + ); + }); + + test('hashCode should not match when the message property is different', + () { + // Arrange + final firstNmeaMessage = NmeaMessage( + "first", + DateTime.fromMillisecondsSinceEpoch(0), + ); + final secondNmeaMessage = NmeaMessage( + "second", + DateTime.fromMillisecondsSinceEpoch(0), + ); + + // Act & Assert + expect( + firstNmeaMessage.hashCode != secondNmeaMessage.hashCode, + true, + ); + }); + + test('hashCode should not match when the timestamp property is different', + () { + // Arrange + final firstNmeaMessage = NmeaMessage( + "message", + DateTime.fromMillisecondsSinceEpoch(0), + ); + final secondNmeaMessage = NmeaMessage( + "message", + DateTime.fromMillisecondsSinceEpoch(1), + ); + + // Act & Assert + expect( + firstNmeaMessage.hashCode != secondNmeaMessage.hashCode, + true, + ); + }); + }); + + group('fromMap tests:', () { + test('fromMap should return null when message is null', () { + // Act + final actual = NmeaMessage.fromMap(null); + + // Assert + expect(actual, null); + }); + + test( + // ignore: lines_longer_than_80_chars + 'fromMap should throw argument error when map does not contain message', + () { + // Arrange + final map = { + 'timestamp': 0, + }; + + // Act & Assert + expect(() => NmeaMessage.fromMap(map), throwsArgumentError); + }); + + test( + // ignore: lines_longer_than_80_chars + 'fromMap should throw argument error when map does not contain timestamp', + () { + // Arrange + final map = { + 'message': "nmeaMessage", + }; + + // Act & Assert + expect(() => NmeaMessage.fromMap(map), throwsArgumentError); + }); + }); +}