From 57a6be561834b4b90c5fc7af24bfc343ef09fd25 Mon Sep 17 00:00:00 2001 From: Ahmed Makled <36972797+Ahmed-Makled@users.noreply.github.com> Date: Thu, 15 May 2025 00:06:33 +0300 Subject: [PATCH 1/5] Fix issue #1574: Add location accuracy filter to prevent GPS drift --- geolocator_android/CHANGELOG.md | 8 + .../location/FusedLocationClient.java | 15 +- .../location/LocationAccuracyFilter.java | 112 ++++++++++ .../location/LocationManagerClient.java | 8 +- .../geolocator/location/LocationOptions.java | 14 +- .../LocationAccuracyFilterTest.java | 204 ++++++++++++++++++ 6 files changed, 356 insertions(+), 5 deletions(-) create mode 100644 geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationAccuracyFilter.java create mode 100644 geolocator_android/android/src/test/java/com/baseflow/geolocator/LocationAccuracyFilterTest.java diff --git a/geolocator_android/CHANGELOG.md b/geolocator_android/CHANGELOG.md index a4067024b..18a37faf7 100644 --- a/geolocator_android/CHANGELOG.md +++ b/geolocator_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## UNRELEASED + +* Adds location accuracy filtering to prevent random GPS drifts (Issue #1574). Features added: + * `LocationAccuracyFilter` class to filter out location updates with unrealistic movement patterns + * Optional filtering through new `enableAccuracyFilter` parameter + * Configurable thresholds for accuracy, speed, and distance jumps + * Comprehensive test coverage for various transportation modes + ## 5.0.1+1 - Bump `androidx.core:core` to version 1.16.0 diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java index a5e3f2b13..04be16c7f 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java @@ -79,7 +79,11 @@ public synchronized void onLocationResult(@NonNull LocationResult locationResult } nmeaClient.enrichExtrasWithNmea(location); - positionChangedCallback.onPositionChanged(location); + + // Apply the location accuracy filter before reporting the position + if (LocationAccuracyFilter.shouldAcceptLocation(location)) { + positionChangedCallback.onPositionChanged(location); + } } @Override @@ -230,6 +234,13 @@ public void startPositionUpdates( this.positionChangedCallback = positionChangedCallback; this.errorCallback = errorCallback; + // Enable or disable the location accuracy filter based on options + if (this.locationOptions != null) { + LocationAccuracyFilter.setFilterEnabled(this.locationOptions.isEnableAccuracyFilter()); + } else { + LocationAccuracyFilter.setFilterEnabled(false); + } + LocationRequest locationRequest = buildLocationRequest(this.locationOptions); LocationSettingsRequest settingsRequest = buildLocationSettingsRequest(locationRequest); @@ -278,5 +289,7 @@ public void startPositionUpdates( public void stopPositionUpdates() { this.nmeaClient.stop(); fusedLocationProviderClient.removeLocationUpdates(locationCallback); + // Reset the location filter when updates are stopped + LocationAccuracyFilter.reset(); } } diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationAccuracyFilter.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationAccuracyFilter.java new file mode 100644 index 000000000..704e096d1 --- /dev/null +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationAccuracyFilter.java @@ -0,0 +1,112 @@ +package com.baseflow.geolocator.location; + +import android.location.Location; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Utility class to filter inaccurate location updates that might cause GPS drift. + * Detects and filters out positions that are physically implausible based on + * speed, distance, and accuracy. + */ +public class LocationAccuracyFilter { + private static final String TAG = "LocationAccuracyFilter"; + + // Constants for filtering + private static final float MAX_ACCURACY_THRESHOLD = 300.0f; // meters + private static final float MAX_SPEED_THRESHOLD = 280.0f; // m/s (~1000 km/h to support aircraft) + private static final float MAX_DISTANCE_JUMP = 1000.0f; // meters + + @Nullable private static Location lastFilteredLocation = null; + private static boolean filterEnabled = false; + + /** + * Sets whether filtering is enabled. + * This should be called when starting location updates based on user preferences. + * + * @param enabled Whether filtering should be enabled + */ + public static void setFilterEnabled(boolean enabled) { + filterEnabled = enabled; + // Reset the filter state when changing the setting + if (enabled) { + reset(); + } + } + + /** + * Checks if a location update should be accepted based on realistic movement patterns. + * + * @param newLocation The new location to evaluate + * @return true if the location should be accepted, false if it should be filtered + */ + public static boolean shouldAcceptLocation(@NonNull Location newLocation) { + // If filtering is disabled, always accept locations + if (!filterEnabled) { + return true; + } + + // Always accept the first position + if (lastFilteredLocation == null) { + lastFilteredLocation = newLocation; + return true; + } + + // Time difference in seconds + float timeDelta = (newLocation.getTime() - lastFilteredLocation.getTime()) / 1000.0f; + + // Don't filter if time hasn't advanced + if (timeDelta <= 0) { + lastFilteredLocation = newLocation; + return true; + } + + // Calculate distance in meters + float distance = newLocation.distanceTo(lastFilteredLocation); + + // Calculate speed (m/s) + float speed = distance / timeDelta; + + // Get position accuracy (if available) + float accuracy = newLocation.hasAccuracy() ? newLocation.getAccuracy() : 0.0f; + + // Filters to apply - use a conservative approach to avoid affecting legitimate use cases + boolean shouldFilter = false; + + // Filter based on very poor accuracy - this catches points with extremely poor accuracy + if (accuracy > MAX_ACCURACY_THRESHOLD) { + Log.d(TAG, "Filtered location: Poor accuracy " + accuracy + "m"); + shouldFilter = true; + } + + // Filter based on unrealistically high speeds + if (speed > MAX_SPEED_THRESHOLD) { + Log.d(TAG, "Filtered location: Unrealistic speed " + speed + "m/s"); + shouldFilter = true; + } + + // Filter based on large jumps when accuracy is moderate or poor + if (distance > MAX_DISTANCE_JUMP && accuracy > 75.0f) { + Log.d(TAG, "Filtered location: Large jump with poor accuracy - distance: " + + distance + "m, accuracy: " + accuracy + "m"); + shouldFilter = true; + } + + // Accept and update reference if it passes filters + if (!shouldFilter) { + lastFilteredLocation = newLocation; + } + + return !shouldFilter; + } + + /** + * Resets the internal state of the filter. + * This should be called when location updates are stopped. + */ + public static void reset() { + lastFilteredLocation = null; + } +} \ No newline at end of file diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationManagerClient.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationManagerClient.java index c8b3e59a0..aede5c9dc 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationManagerClient.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationManagerClient.java @@ -170,6 +170,11 @@ public void startPositionUpdates( ? LocationRequestCompat.PASSIVE_INTERVAL : locationOptions.getTimeInterval(); quality = accuracyToQuality(accuracy); + + // Enable or disable the location accuracy filter based on options + LocationAccuracyFilter.setFilterEnabled(locationOptions.isEnableAccuracyFilter()); + } else { + LocationAccuracyFilter.setFilterEnabled(false); } this.currentLocationProvider = determineProvider(this.locationManager, accuracy); @@ -202,11 +207,12 @@ public void stopPositionUpdates() { this.isListening = false; this.nmeaClient.stop(); this.locationManager.removeUpdates(this); + LocationAccuracyFilter.reset(); } @Override public synchronized void onLocationChanged(Location location) { - if (isBetterLocation(location, currentBestLocation)) { + if (isBetterLocation(location, currentBestLocation) && LocationAccuracyFilter.shouldAcceptLocation(location)) { this.currentBestLocation = location; if (this.positionChangedCallback != null) { diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationOptions.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationOptions.java index 9087c8270..4e2fe16b7 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationOptions.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationOptions.java @@ -9,24 +9,27 @@ public class LocationOptions { private final long distanceFilter; private final long timeInterval; private final boolean useMSLAltitude; + private final boolean enableAccuracyFilter; private LocationOptions( - LocationAccuracy accuracy, long distanceFilter, long timeInterval, boolean useMSLAltitude) { + LocationAccuracy accuracy, long distanceFilter, long timeInterval, boolean useMSLAltitude, boolean enableAccuracyFilter) { this.accuracy = accuracy; this.distanceFilter = distanceFilter; this.timeInterval = timeInterval; this.useMSLAltitude = useMSLAltitude; + this.enableAccuracyFilter = enableAccuracyFilter; } public static LocationOptions parseArguments(Map arguments) { if (arguments == null) { - return new LocationOptions(LocationAccuracy.best, 0, 5000, false); + return new LocationOptions(LocationAccuracy.best, 0, 5000, false, false); } final Integer accuracy = (Integer) arguments.get("accuracy"); final Integer distanceFilter = (Integer) arguments.get("distanceFilter"); final Integer timeInterval = (Integer) arguments.get("timeInterval"); final Boolean useMSLAltitude = (Boolean) arguments.get("useMSLAltitude"); + final Boolean enableAccuracyFilter = (Boolean) arguments.get("enableAccuracyFilter"); LocationAccuracy locationAccuracy = LocationAccuracy.best; @@ -57,7 +60,8 @@ public static LocationOptions parseArguments(Map arguments) { locationAccuracy, distanceFilter != null ? distanceFilter : 0, timeInterval != null ? timeInterval : 5000, - useMSLAltitude != null && useMSLAltitude); + useMSLAltitude != null && useMSLAltitude, + enableAccuracyFilter != null && enableAccuracyFilter); } public LocationAccuracy getAccuracy() { @@ -75,4 +79,8 @@ public long getTimeInterval() { public boolean isUseMSLAltitude() { return useMSLAltitude; } + + public boolean isEnableAccuracyFilter() { + return enableAccuracyFilter; + } } diff --git a/geolocator_android/android/src/test/java/com/baseflow/geolocator/LocationAccuracyFilterTest.java b/geolocator_android/android/src/test/java/com/baseflow/geolocator/LocationAccuracyFilterTest.java new file mode 100644 index 000000000..3faf71084 --- /dev/null +++ b/geolocator_android/android/src/test/java/com/baseflow/geolocator/LocationAccuracyFilterTest.java @@ -0,0 +1,204 @@ +package com.baseflow.geolocator; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import android.location.Location; + +import com.baseflow.geolocator.location.LocationAccuracyFilter; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class LocationAccuracyFilterTest { + @Mock private Location mockLocation1; + @Mock private Location mockLocation2; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + // Reset the filter state before each test + LocationAccuracyFilter.reset(); + // Enable filtering by default for tests + LocationAccuracyFilter.setFilterEnabled(true); + } + + @Test + public void shouldAcceptLocation_withFirstLocation_returnsTrue() { + // Arrange + when(mockLocation1.getTime()).thenReturn(1000L); + when(mockLocation1.hasAccuracy()).thenReturn(true); + when(mockLocation1.getAccuracy()).thenReturn(10.0f); + + // Act & Assert + assertTrue(LocationAccuracyFilter.shouldAcceptLocation(mockLocation1)); + } + + @Test + public void shouldAcceptLocation_withPoorAccuracy_returnsFalse() { + // Arrange - First accepted location + when(mockLocation1.getTime()).thenReturn(1000L); + when(mockLocation1.hasAccuracy()).thenReturn(true); + when(mockLocation1.getAccuracy()).thenReturn(10.0f); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Arrange - Second location with poor accuracy + when(mockLocation2.getTime()).thenReturn(2000L); + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(350.0f); // Above threshold + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(50.0f); + + // Act & Assert + assertFalse(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + } + + @Test + public void shouldAcceptLocation_withUnrealisticSpeed_returnsFalse() { + // Arrange - First accepted location + when(mockLocation1.getTime()).thenReturn(1000L); + when(mockLocation1.hasAccuracy()).thenReturn(true); + when(mockLocation1.getAccuracy()).thenReturn(10.0f); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Arrange - Second location with unrealistic speed (5000m in 1 second = 5000 m/s) + when(mockLocation2.getTime()).thenReturn(2000L); + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(10.0f); + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(5000.0f); + + // Act & Assert + assertFalse(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + } + + @Test + public void shouldAcceptLocation_withLargeJumpAndModerateAccuracy_returnsFalse() { + // Arrange - First accepted location + when(mockLocation1.getTime()).thenReturn(1000L); + when(mockLocation1.hasAccuracy()).thenReturn(true); + when(mockLocation1.getAccuracy()).thenReturn(10.0f); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Arrange - Second location with large jump and moderate accuracy + when(mockLocation2.getTime()).thenReturn(2000L); + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(80.0f); // Moderate accuracy + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(2000.0f); // Large jump + + // Act & Assert + assertFalse(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + } + + @Test + public void shouldAcceptLocation_withNormalMovement_returnsTrue() { + // Arrange - First accepted location + when(mockLocation1.getTime()).thenReturn(1000L); + when(mockLocation1.hasAccuracy()).thenReturn(true); + when(mockLocation1.getAccuracy()).thenReturn(10.0f); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Arrange - Second location with normal movement (50m in 10 seconds = 5 m/s) + when(mockLocation2.getTime()).thenReturn(11000L); + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(10.0f); + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(50.0f); + + // Act & Assert + assertTrue(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + } + + @Test + public void shouldAcceptLocation_withResetBetweenLocations_returnsTrue() { + // Arrange - First accepted location + when(mockLocation1.getTime()).thenReturn(1000L); + when(mockLocation1.hasAccuracy()).thenReturn(true); + when(mockLocation1.getAccuracy()).thenReturn(10.0f); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Reset the filter + LocationAccuracyFilter.reset(); + + // Arrange - Second location that would normally be rejected (5000m in 1 second = 5000 m/s) + when(mockLocation2.getTime()).thenReturn(2000L); + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(10.0f); + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(5000.0f); + + // Act & Assert - Should be accepted because filter was reset + assertTrue(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + } + + @Test + public void shouldAcceptLocation_withRealisticTransportationSpeeds() { + // Arrange - First accepted location + when(mockLocation1.getTime()).thenReturn(1000L); + when(mockLocation1.hasAccuracy()).thenReturn(true); + when(mockLocation1.getAccuracy()).thenReturn(10.0f); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Test case 1: Airplane speed (900 km/h = 250 m/s) + // 1000 meters in 4 seconds = 250 m/s + when(mockLocation2.getTime()).thenReturn(5000L); // 4 seconds later + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(15.0f); // Good accuracy + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(1000.0f); + + // Should NOT accept airplane-like movement (exceeds threshold of 120 m/s) + assertFalse(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + + // Reset for next test + LocationAccuracyFilter.reset(); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Test case 2: High-speed train (300 km/h = 83.3 m/s) + // 833 meters in 10 seconds = 83.3 m/s + when(mockLocation2.getTime()).thenReturn(11000L); // 10 seconds later + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(15.0f); // Good accuracy + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(833.0f); + + // Should accept high-speed train movement + assertTrue(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + + // Reset for next test + LocationAccuracyFilter.reset(); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Test case 3: Fast car on highway (120 km/h = 33.3 m/s) + // 333 meters in 10 seconds = 33.3 m/s + when(mockLocation2.getTime()).thenReturn(11000L); // 10 seconds later + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(15.0f); // Good accuracy + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(333.0f); + + // Should accept fast car movement + assertTrue(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + } + + @Test + public void shouldAcceptLocation_whenFilterDisabled_alwaysReturnsTrue() { + // Arrange + // Disable filtering + LocationAccuracyFilter.setFilterEnabled(false); + + // Set up first location + when(mockLocation1.getTime()).thenReturn(1000L); + when(mockLocation1.hasAccuracy()).thenReturn(true); + when(mockLocation1.getAccuracy()).thenReturn(10.0f); + LocationAccuracyFilter.shouldAcceptLocation(mockLocation1); + + // Set up second location that would normally be filtered (unrealistic speed) + when(mockLocation2.getTime()).thenReturn(2000L); + when(mockLocation2.hasAccuracy()).thenReturn(true); + when(mockLocation2.getAccuracy()).thenReturn(10.0f); + when(mockLocation2.distanceTo(mockLocation1)).thenReturn(5000.0f); // 5000m in 1s = 5000 m/s + + // Act & Assert - Should be accepted because filtering is disabled + assertTrue(LocationAccuracyFilter.shouldAcceptLocation(mockLocation2)); + + // Make sure to enable filtering again for other tests + LocationAccuracyFilter.setFilterEnabled(true); + } +} \ No newline at end of file From d0505a05e61b35d467d1f8f09885d9f6015bb432 Mon Sep 17 00:00:00 2001 From: Ahmed Makled <36972797+Ahmed-Makled@users.noreply.github.com> Date: Thu, 15 May 2025 00:38:27 +0300 Subject: [PATCH 2/5] Add enableAccuracyFilter option to AndroidSettings class --- .../lib/src/types/android_settings.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/geolocator_android/lib/src/types/android_settings.dart b/geolocator_android/lib/src/types/android_settings.dart index 0cd399237..ee770c7df 100644 --- a/geolocator_android/lib/src/types/android_settings.dart +++ b/geolocator_android/lib/src/types/android_settings.dart @@ -9,6 +9,7 @@ class AndroidSettings extends LocationSettings { /// /// The following default values are used: /// - forceLocationManager: false + /// - enableAccuracyFilter: false AndroidSettings({ this.forceLocationManager = false, super.accuracy, @@ -17,6 +18,7 @@ class AndroidSettings extends LocationSettings { super.timeLimit, this.foregroundNotificationConfig, this.useMSLAltitude = false, + this.enableAccuracyFilter = false, }); /// Forces the Geolocator plugin to use the legacy LocationManager instead of @@ -71,6 +73,21 @@ class AndroidSettings extends LocationSettings { /// Defaults to false final bool useMSLAltitude; + /// Enables filtering for inaccurate GPS positions that might cause random GPS drift. + /// + /// When enabled, the plugin will filter out location updates that are physically implausible + /// based on speed, distance jumps, and accuracy thresholds. This is useful for applications + /// that require smooth location tracking without sudden jumps that can occur due to GPS + /// inaccuracies. + /// + /// The filter uses the following criteria to filter out problematic positions: + /// - Locations with very poor accuracy (> 300 meters) + /// - Unrealistically high speeds (> 280 m/s or ~1000 km/h) + /// - Large position jumps combined with poor accuracy + /// + /// Defaults to false + final bool enableAccuracyFilter; + @override Map toJson() { return super.toJson() @@ -79,6 +96,7 @@ class AndroidSettings extends LocationSettings { 'timeInterval': intervalDuration?.inMilliseconds, 'foregroundNotificationConfig': foregroundNotificationConfig?.toJson(), 'useMSLAltitude': useMSLAltitude, + 'enableAccuracyFilter': enableAccuracyFilter, }); } } From ac62031808a3a3b4e1307e7cb7b556450e266ab1 Mon Sep 17 00:00:00 2001 From: Ahmed Makled <36972797+Ahmed-Makled@users.noreply.github.com> Date: Thu, 15 May 2025 02:06:01 +0300 Subject: [PATCH 3/5] Fix formatting issues in geolocator_android files --- geolocator_android/example/lib/main.dart | 292 ++- .../lib/src/types/android_position.dart | 36 +- .../lib/src/types/android_settings.dart | 17 +- .../test/event_channel_mock.dart | 37 +- .../test/geolocator_android_test.dart | 1785 ++++++++--------- .../test/method_channel_mock.dart | 16 +- 6 files changed, 1064 insertions(+), 1119 deletions(-) diff --git a/geolocator_android/example/lib/main.dart b/geolocator_android/example/lib/main.dart index 3934a1020..0fccaf928 100644 --- a/geolocator_android/example/lib/main.dart +++ b/geolocator_android/example/lib/main.dart @@ -9,7 +9,8 @@ import 'package:geolocator_platform_interface/geolocator_platform_interface.dart /// Defines the main theme color. final MaterialColor themeMaterialColor = BaseflowPluginExample.createMaterialColor( - const Color.fromRGBO(48, 49, 60, 1)); + const Color.fromRGBO(48, 49, 60, 1), + ); void main() { runApp(const GeolocatorWidget()); @@ -18,14 +19,14 @@ void main() { /// Example [Widget] showing the functionalities of the geolocator plugin. class GeolocatorWidget extends StatefulWidget { /// Creates a [PermissionHandlerWidget]. - const GeolocatorWidget({ - super.key, - }); + const GeolocatorWidget({super.key}); /// Create a page containing the functionality of this plugin static ExamplePage createPage() { return ExamplePage( - Icons.location_on, (context) => const GeolocatorWidget()); + Icons.location_on, + (context) => const GeolocatorWidget(), + ); } @override @@ -77,110 +78,103 @@ class _GeolocatorWidgetState extends State { break; } }, - itemBuilder: (context) => [ - const PopupMenuItem( - value: 1, - child: Text("Get Location Accuracy"), - ), - if (Platform.isIOS) - const PopupMenuItem( - value: 2, - child: Text("Request Temporary Full Accuracy"), - ), - const PopupMenuItem( - value: 3, - child: Text("Open App Settings"), - ), - if (Platform.isAndroid) - const PopupMenuItem( - value: 4, - child: Text("Open Location Settings"), - ), - const PopupMenuItem( - value: 5, - child: Text("Clear"), - ), - ], + itemBuilder: + (context) => [ + const PopupMenuItem(value: 1, child: Text("Get Location Accuracy")), + if (Platform.isIOS) + const PopupMenuItem( + value: 2, + child: Text("Request Temporary Full Accuracy"), + ), + const PopupMenuItem(value: 3, child: Text("Open App Settings")), + if (Platform.isAndroid) + const PopupMenuItem( + value: 4, + child: Text("Open Location Settings"), + ), + const PopupMenuItem(value: 5, child: Text("Clear")), + ], ); } @override Widget build(BuildContext context) { - const sizedBox = SizedBox( - height: 10, - ); + const sizedBox = SizedBox(height: 10); return BaseflowPluginExample( - pluginName: 'Geolocator', - githubURL: 'https://github.com/Baseflow/flutter-geolocator', - pubDevURL: 'https://pub.dev/packages/geolocator', - appBarActions: [ - _createActions() - ], - pages: [ - ExamplePage( - Icons.location_on, - (context) => Scaffold( - backgroundColor: Theme.of(context).colorScheme.surface, - body: ListView.builder( - itemCount: _positionItems.length, - itemBuilder: (context, index) { - final positionItem = _positionItems[index]; - - if (positionItem.type == _PositionItemType.log) { - return ListTile( - title: Text(positionItem.displayValue, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - )), - ); - } else { - return Card( - child: ListTile( - tileColor: themeMaterialColor, - title: Text( - positionItem.displayValue, - style: const TextStyle(color: Colors.white), - ), + pluginName: 'Geolocator', + githubURL: 'https://github.com/Baseflow/flutter-geolocator', + pubDevURL: 'https://pub.dev/packages/geolocator', + appBarActions: [_createActions()], + pages: [ + ExamplePage( + Icons.location_on, + (context) => Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + body: ListView.builder( + itemCount: _positionItems.length, + itemBuilder: (context, index) { + final positionItem = _positionItems[index]; + + if (positionItem.type == _PositionItemType.log) { + return ListTile( + title: Text( + positionItem.displayValue, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, ), - ); - } - }, - ), - floatingActionButton: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FloatingActionButton( - onPressed: _toggleListening, - tooltip: (_positionStreamSubscription == null) - ? 'Start position updates' - : _positionStreamSubscription!.isPaused - ? 'Resume' - : 'Pause', - backgroundColor: _determineButtonColor(), - child: (_positionStreamSubscription == null || - _positionStreamSubscription!.isPaused) - ? const Icon(Icons.play_arrow) - : const Icon(Icons.pause), - ), - sizedBox, - FloatingActionButton( - onPressed: _getCurrentPosition, - child: const Icon(Icons.my_location), - ), - sizedBox, - FloatingActionButton( - onPressed: _getLastKnownPosition, - child: const Icon(Icons.bookmark), - ), - ], - ), + ), + ); + } else { + return Card( + child: ListTile( + tileColor: themeMaterialColor, + title: Text( + positionItem.displayValue, + style: const TextStyle(color: Colors.white), + ), + ), + ); + } + }, ), - ) - ]); + floatingActionButton: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FloatingActionButton( + onPressed: _toggleListening, + tooltip: + (_positionStreamSubscription == null) + ? 'Start position updates' + : _positionStreamSubscription!.isPaused + ? 'Resume' + : 'Pause', + backgroundColor: _determineButtonColor(), + child: + (_positionStreamSubscription == null || + _positionStreamSubscription!.isPaused) + ? const Icon(Icons.play_arrow) + : const Icon(Icons.pause), + ), + sizedBox, + FloatingActionButton( + onPressed: _getCurrentPosition, + child: const Icon(Icons.my_location), + ), + sizedBox, + FloatingActionButton( + onPressed: _getLastKnownPosition, + child: const Icon(Icons.bookmark), + ), + ], + ), + ), + ), + ], + ); } Future _getCurrentPosition() async { @@ -191,10 +185,7 @@ class _GeolocatorWidgetState extends State { } final position = await geolocatorAndroid.getCurrentPosition(); - _updatePositionList( - _PositionItemType.position, - position.toString(), - ); + _updatePositionList(_PositionItemType.position, position.toString()); } Future _handlePermission() async { @@ -224,10 +215,7 @@ class _GeolocatorWidgetState extends State { // Android's shouldShowRequestPermissionRationale // returned true. According to Android guidelines // your App should show an explanatory UI now. - _updatePositionList( - _PositionItemType.log, - _kPermissionDeniedMessage, - ); + _updatePositionList(_PositionItemType.log, _kPermissionDeniedMessage); return false; } @@ -245,10 +233,7 @@ class _GeolocatorWidgetState extends State { // When we reach here, permissions are granted and we can // continue accessing the position of the device. - _updatePositionList( - _PositionItemType.log, - _kPermissionGrantedMessage, - ); + _updatePositionList(_PositionItemType.log, _kPermissionGrantedMessage); return true; } @@ -257,8 +242,9 @@ class _GeolocatorWidgetState extends State { setState(() {}); } - bool _isListening() => !(_positionStreamSubscription == null || - _positionStreamSubscription!.isPaused); + bool _isListening() => + !(_positionStreamSubscription == null || + _positionStreamSubscription!.isPaused); Color _determineButtonColor() { return _isListening() ? Colors.green : Colors.red; @@ -267,22 +253,23 @@ class _GeolocatorWidgetState extends State { void _toggleServiceStatusStream() { if (_serviceStatusStreamSubscription == null) { final serviceStatusStream = geolocatorAndroid.getServiceStatusStream(); - _serviceStatusStreamSubscription = - serviceStatusStream.handleError((error) { - _serviceStatusStreamSubscription?.cancel(); - _serviceStatusStreamSubscription = null; - }).listen((serviceStatus) { - String serviceStatusValue; - if (serviceStatus == ServiceStatus.enabled) { - serviceStatusValue = 'enabled'; - } else { - serviceStatusValue = 'disabled'; - } - _updatePositionList( - _PositionItemType.log, - 'Location service has been $serviceStatusValue', - ); - }); + _serviceStatusStreamSubscription = serviceStatusStream + .handleError((error) { + _serviceStatusStreamSubscription?.cancel(); + _serviceStatusStreamSubscription = null; + }) + .listen((serviceStatus) { + String serviceStatusValue; + if (serviceStatus == ServiceStatus.enabled) { + serviceStatusValue = 'enabled'; + } else { + serviceStatusValue = 'disabled'; + } + _updatePositionList( + _PositionItemType.log, + 'Location service has been $serviceStatusValue', + ); + }); } } @@ -313,17 +300,20 @@ class _GeolocatorWidgetState extends State { ), ); final positionStream = geolocatorAndroid.getPositionStream( - locationSettings: androidSettings); - _positionStreamSubscription = positionStream.handleError((error) { - _positionStreamSubscription?.cancel(); - _positionStreamSubscription = null; - }).listen((position) { - debugPrint(position.altitude.toString()); - _updatePositionList( - _PositionItemType.position, - position.toString(), - ); - }); + locationSettings: androidSettings, + ); + _positionStreamSubscription = positionStream + .handleError((error) { + _positionStreamSubscription?.cancel(); + _positionStreamSubscription = null; + }) + .listen((position) { + debugPrint(position.altitude.toString()); + _updatePositionList( + _PositionItemType.position, + position.toString(), + ); + }); _positionStreamSubscription?.pause(); } @@ -361,10 +351,7 @@ class _GeolocatorWidgetState extends State { void _getLastKnownPosition() async { final position = await geolocatorAndroid.getLastKnownPosition(); if (position != null) { - _updatePositionList( - _PositionItemType.position, - position.toString(), - ); + _updatePositionList(_PositionItemType.position, position.toString()); } else { _updatePositionList( _PositionItemType.log, @@ -410,10 +397,7 @@ class _GeolocatorWidgetState extends State { displayValue = 'Error opening Application Settings.'; } - _updatePositionList( - _PositionItemType.log, - displayValue, - ); + _updatePositionList(_PositionItemType.log, displayValue); } void _openLocationSettings() async { @@ -426,17 +410,11 @@ class _GeolocatorWidgetState extends State { displayValue = 'Error opening Location Settings'; } - _updatePositionList( - _PositionItemType.log, - displayValue, - ); + _updatePositionList(_PositionItemType.log, displayValue); } } -enum _PositionItemType { - log, - position, -} +enum _PositionItemType { log, position } class _PositionItem { _PositionItem(this.type, this.displayValue); diff --git a/geolocator_android/lib/src/types/android_position.dart b/geolocator_android/lib/src/types/android_position.dart index d44396b57..137e39d41 100644 --- a/geolocator_android/lib/src/types/android_position.dart +++ b/geolocator_android/lib/src/types/android_position.dart @@ -26,18 +26,18 @@ class AndroidPosition extends Position { super.floor, isMocked = false, }) : super( - longitude: longitude, - latitude: latitude, - timestamp: timestamp, - accuracy: accuracy, - altitude: altitude, - altitudeAccuracy: altitudeAccuracy, - heading: heading, - headingAccuracy: headingAccuracy, - speed: speed, - speedAccuracy: speedAccuracy, - isMocked: isMocked, - ); + longitude: longitude, + latitude: latitude, + timestamp: timestamp, + accuracy: accuracy, + altitude: altitude, + altitudeAccuracy: altitudeAccuracy, + heading: heading, + headingAccuracy: headingAccuracy, + speed: speed, + speedAccuracy: speedAccuracy, + isMocked: isMocked, + ); /// If available it returns the number of GNSS satellites. /// @@ -51,7 +51,8 @@ class AndroidPosition extends Position { @override bool operator ==(Object other) { - var areEqual = other is AndroidPosition && + var areEqual = + other is AndroidPosition && super == other && other.satelliteCount == satelliteCount && other.satellitesUsedInFix == satellitesUsedInFix; @@ -90,10 +91,9 @@ class AndroidPosition extends Position { /// serialized to JSON. @override Map toJson() { - return super.toJson() - ..addAll({ - 'gnss_satellite_count': satelliteCount, - 'gnss_satellites_used_in_fix': satellitesUsedInFix, - }); + return super.toJson()..addAll({ + 'gnss_satellite_count': satelliteCount, + 'gnss_satellites_used_in_fix': satellitesUsedInFix, + }); } } diff --git a/geolocator_android/lib/src/types/android_settings.dart b/geolocator_android/lib/src/types/android_settings.dart index ee770c7df..61eb766fe 100644 --- a/geolocator_android/lib/src/types/android_settings.dart +++ b/geolocator_android/lib/src/types/android_settings.dart @@ -74,7 +74,7 @@ class AndroidSettings extends LocationSettings { final bool useMSLAltitude; /// Enables filtering for inaccurate GPS positions that might cause random GPS drift. - /// + /// /// When enabled, the plugin will filter out location updates that are physically implausible /// based on speed, distance jumps, and accuracy thresholds. This is useful for applications /// that require smooth location tracking without sudden jumps that can occur due to GPS @@ -90,13 +90,12 @@ class AndroidSettings extends LocationSettings { @override Map toJson() { - return super.toJson() - ..addAll({ - 'forceLocationManager': forceLocationManager, - 'timeInterval': intervalDuration?.inMilliseconds, - 'foregroundNotificationConfig': foregroundNotificationConfig?.toJson(), - 'useMSLAltitude': useMSLAltitude, - 'enableAccuracyFilter': enableAccuracyFilter, - }); + return super.toJson()..addAll({ + 'forceLocationManager': forceLocationManager, + 'timeInterval': intervalDuration?.inMilliseconds, + 'foregroundNotificationConfig': foregroundNotificationConfig?.toJson(), + 'useMSLAltitude': useMSLAltitude, + 'enableAccuracyFilter': enableAccuracyFilter, + }); } } diff --git a/geolocator_android/test/event_channel_mock.dart b/geolocator_android/test/event_channel_mock.dart index afe77e6a9..d45828784 100644 --- a/geolocator_android/test/event_channel_mock.dart +++ b/geolocator_android/test/event_channel_mock.dart @@ -10,10 +10,8 @@ class EventChannelMock { Stream? stream; StreamSubscription? _streamSubscription; - EventChannelMock({ - required String channelName, - required this.stream, - }) : _methodChannel = MethodChannel(channelName) { + EventChannelMock({required String channelName, required this.stream}) + : _methodChannel = MethodChannel(channelName) { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(_methodChannel, _handler); } @@ -36,14 +34,16 @@ class EventChannelMock { } void _onListen() { - _streamSubscription = stream!.handleError((e) { - _sendErrorEnvelope(e); - }).listen( - _sendSuccessEnvelope, - onDone: () { - _sendEnvelope(null); - }, - ); + _streamSubscription = stream! + .handleError((e) { + _sendErrorEnvelope(e); + }) + .listen( + _sendSuccessEnvelope, + onDone: () { + _sendEnvelope(null); + }, + ); } void _onCancel() { @@ -64,8 +64,11 @@ class EventChannelMock { details = error.details; } - final envelope = const StandardMethodCodec() - .encodeErrorEnvelope(code: code, message: message, details: details); + final envelope = const StandardMethodCodec().encodeErrorEnvelope( + code: code, + message: message, + details: details, + ); _sendEnvelope(envelope); } @@ -77,10 +80,6 @@ class EventChannelMock { void _sendEnvelope(ByteData? envelope) { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .handlePlatformMessage( - _methodChannel.name, - envelope, - (_) {}, - ); + .handlePlatformMessage(_methodChannel.name, envelope, (_) {}); } } diff --git a/geolocator_android/test/geolocator_android_test.dart b/geolocator_android/test/geolocator_android_test.dart index 67c8a60c6..c3431fd7d 100644 --- a/geolocator_android/test/geolocator_android_test.dart +++ b/geolocator_android/test/geolocator_android_test.dart @@ -10,22 +10,20 @@ import 'event_channel_mock.dart'; import 'method_channel_mock.dart'; Position get mockPosition => AndroidPosition( - latitude: 52.561270, - longitude: 5.639382, - timestamp: DateTime.fromMillisecondsSinceEpoch( - 500, - isUtc: true, - ), - altitude: 3000.0, - altitudeAccuracy: 0.0, - satelliteCount: 2.0, - satellitesUsedInFix: 2.0, - accuracy: 0.0, - heading: 0.0, - headingAccuracy: 0.0, - speed: 0.0, - speedAccuracy: 0.0, - isMocked: false); + latitude: 52.561270, + longitude: 5.639382, + timestamp: DateTime.fromMillisecondsSinceEpoch(500, isUtc: true), + altitude: 3000.0, + altitudeAccuracy: 0.0, + satelliteCount: 2.0, + satellitesUsedInFix: 2.0, + accuracy: 0.0, + heading: 0.0, + headingAccuracy: 0.0, + speed: 0.0, + speedAccuracy: 0.0, + isMocked: false, +); void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -37,29 +35,27 @@ void main() { group('checkPermission: When checking for permission', () { test( - // ignore: lines_longer_than_80_chars - 'Should receive whenInUse if permission is granted when App is in use', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'checkPermission', - result: LocationPermission.whileInUse.index, - ), - ], - ); + // ignore: lines_longer_than_80_chars + 'Should receive whenInUse if permission is granted when App is in use', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'checkPermission', + result: LocationPermission.whileInUse.index, + ), + ], + ); - // Act - final permission = await GeolocatorAndroid().checkPermission(); + // Act + final permission = await GeolocatorAndroid().checkPermission(); - // Assert - expect( - permission, - LocationPermission.whileInUse, - ); - }); + // Assert + expect(permission, LocationPermission.whileInUse); + }, + ); test('Should receive always if permission is granted always', () async { // Arrange @@ -77,10 +73,7 @@ void main() { final permission = await GeolocatorAndroid().checkPermission(); // Assert - expect( - permission, - LocationPermission.always, - ); + expect(permission, LocationPermission.always); }); test('Should receive denied if permission is denied', () async { @@ -99,83 +92,76 @@ void main() { final permission = await GeolocatorAndroid().checkPermission(); // Assert - expect( - permission, - LocationPermission.denied, - ); + expect(permission, LocationPermission.denied); }); - test('Should receive deniedForEver if permission is denied for ever', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'checkPermission', - result: LocationPermission.deniedForever.index, - ), - ], - ); + test( + 'Should receive deniedForEver if permission is denied for ever', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'checkPermission', + result: LocationPermission.deniedForever.index, + ), + ], + ); - // Act - final permission = await GeolocatorAndroid().checkPermission(); + // Act + final permission = await GeolocatorAndroid().checkPermission(); - // Assert - expect( - permission, - LocationPermission.deniedForever, - ); - }); + // Assert + expect(permission, LocationPermission.deniedForever); + }, + ); - test('Should receive an exception when permission definitions not found', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'checkPermission', - result: PlatformException( - code: 'PERMISSION_DEFINITIONS_NOT_FOUND', - message: 'Permission definitions are not found.', - details: null, + test( + 'Should receive an exception when permission definitions not found', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'checkPermission', + result: PlatformException( + code: 'PERMISSION_DEFINITIONS_NOT_FOUND', + message: 'Permission definitions are not found.', + details: null, + ), ), - ), - ], - ); + ], + ); - // Act - final permissionFuture = GeolocatorAndroid().checkPermission(); + // Act + final permissionFuture = GeolocatorAndroid().checkPermission(); - // Assert - expect( - permissionFuture, - throwsA( - isA().having( - (e) => e.message, - 'description', - 'Permission definitions are not found.', + // Assert + expect( + permissionFuture, + throwsA( + isA().having( + (e) => e.message, + 'description', + 'Permission definitions are not found.', + ), ), - ), - ); - }); + ); + }, + ); }); - group( - 'requestTemporaryFullAccuracy: When requesting temporary full' + group('requestTemporaryFullAccuracy: When requesting temporary full' 'accuracy.', () { - test( - 'Should receive reduced accuracy if Location Accuracy is pinned to' + test('Should receive reduced accuracy if Location Accuracy is pinned to' ' reduced', () async { // Arrange final methodChannel = MethodChannelMock( channelName: 'flutter.baseflow.com/geolocator_android', methods: const [ - MethodMock( - methodName: 'requestTemporaryFullAccuracy', - result: 0, - ), + MethodMock(methodName: 'requestTemporaryFullAccuracy', result: 0), ], ); @@ -199,133 +185,131 @@ void main() { ]); }); - test( - 'Should receive reduced accuracy if Location Accuracy is already set' + test('Should receive reduced accuracy if Location Accuracy is already set' ' to precise location accuracy', () async { // Arrange MethodChannelMock( channelName: 'flutter.baseflow.com/geolocator_android', methods: const [ - MethodMock( - methodName: 'requestTemporaryFullAccuracy', - result: 1, - ), + MethodMock(methodName: 'requestTemporaryFullAccuracy', result: 1), ], ); // Act - final accuracy = await GeolocatorAndroid() - .requestTemporaryFullAccuracy(purposeKey: 'purposeKey'); + final accuracy = await GeolocatorAndroid().requestTemporaryFullAccuracy( + purposeKey: 'purposeKey', + ); // Assert expect(accuracy, LocationAccuracyStatus.precise); }); - test('Should receive an exception when permission definitions not found', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'requestTemporaryFullAccuracy', - result: PlatformException( - code: 'PERMISSION_DEFINITIONS_NOT_FOUND', - message: 'Permission definitions are not found.', - details: null, + test( + 'Should receive an exception when permission definitions not found', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'requestTemporaryFullAccuracy', + result: PlatformException( + code: 'PERMISSION_DEFINITIONS_NOT_FOUND', + message: 'Permission definitions are not found.', + details: null, + ), ), - ), - ], - ); + ], + ); - // Act - final future = GeolocatorAndroid() - .requestTemporaryFullAccuracy(purposeKey: 'purposeKey'); + // Act + final future = GeolocatorAndroid().requestTemporaryFullAccuracy( + purposeKey: 'purposeKey', + ); - // Assert - expect( - future, - throwsA( - isA().having( - (e) => e.message, - 'description', - 'Permission definitions are not found.', + // Assert + expect( + future, + throwsA( + isA().having( + (e) => e.message, + 'description', + 'Permission definitions are not found.', + ), ), - ), - ); - }); + ); + }, + ); }); - group('getLocationAccuracy: When requesting the Location Accuracy Status', - () { - test('Should receive reduced accuracy if Location Accuracy is reduced', + group( + 'getLocationAccuracy: When requesting the Location Accuracy Status', + () { + test( + 'Should receive reduced accuracy if Location Accuracy is reduced', () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: const [ - MethodMock( - methodName: 'getLocationAccuracy', - result: 0, - ), - ], - ); + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: const [ + MethodMock(methodName: 'getLocationAccuracy', result: 0), + ], + ); - // Act - final locationAccuracy = - await GeolocatorAndroid().getLocationAccuracy(); + // Act + final locationAccuracy = + await GeolocatorAndroid().getLocationAccuracy(); - // Assert - expect(locationAccuracy, LocationAccuracyStatus.reduced); - }); + // Assert + expect(locationAccuracy, LocationAccuracyStatus.reduced); + }, + ); - test('Should receive reduced accuracy if Location Accuracy is reduced', + test( + 'Should receive reduced accuracy if Location Accuracy is reduced', () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: const [ - MethodMock( - methodName: 'getLocationAccuracy', - result: 1, - ), - ], - ); + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: const [ + MethodMock(methodName: 'getLocationAccuracy', result: 1), + ], + ); - // Act - final locationAccuracy = - await GeolocatorAndroid().getLocationAccuracy(); + // Act + final locationAccuracy = + await GeolocatorAndroid().getLocationAccuracy(); - // Assert - expect(locationAccuracy, LocationAccuracyStatus.precise); - }); - }); + // Assert + expect(locationAccuracy, LocationAccuracyStatus.precise); + }, + ); + }, + ); group('requestPermission: When requesting for permission', () { test( - // ignore: lines_longer_than_80_chars - 'Should receive whenInUse if permission is granted when App is in use', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'requestPermission', - result: LocationPermission.whileInUse.index, - ), - ], - ); + // ignore: lines_longer_than_80_chars + 'Should receive whenInUse if permission is granted when App is in use', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'requestPermission', + result: LocationPermission.whileInUse.index, + ), + ], + ); - // Act - final permission = await GeolocatorAndroid().requestPermission(); + // Act + final permission = await GeolocatorAndroid().requestPermission(); - // Assert - expect( - permission, - LocationPermission.whileInUse, - ); - }); + // Assert + expect(permission, LocationPermission.whileInUse); + }, + ); test('Should receive always if permission is granted always', () async { // Arrange @@ -343,10 +327,7 @@ void main() { final permission = await GeolocatorAndroid().requestPermission(); // Assert - expect( - permission, - LocationPermission.always, - ); + expect(permission, LocationPermission.always); }); test('Should receive denied if permission is denied', () async { @@ -357,7 +338,7 @@ void main() { MethodMock( methodName: 'requestPermission', result: LocationPermission.denied.index, - ) + ), ], ); @@ -365,183 +346,181 @@ void main() { final permission = await GeolocatorAndroid().requestPermission(); // Assert - expect( - permission, - LocationPermission.denied, - ); + expect(permission, LocationPermission.denied); }); - test('Should receive deniedForever if permission is denied for ever', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'requestPermission', - result: LocationPermission.deniedForever.index, - ), - ], - ); + test( + 'Should receive deniedForever if permission is denied for ever', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'requestPermission', + result: LocationPermission.deniedForever.index, + ), + ], + ); - // Act - final permission = await GeolocatorAndroid().requestPermission(); + // Act + final permission = await GeolocatorAndroid().requestPermission(); - // Assert - expect( - permission, - LocationPermission.deniedForever, - ); - }); + // Assert + expect(permission, LocationPermission.deniedForever); + }, + ); - test('Should receive an exception when already requesting permission', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'requestPermission', - result: PlatformException( - code: "PERMISSION_REQUEST_IN_PROGRESS", - message: "Permissions already being requested.", - details: null, + test( + 'Should receive an exception when already requesting permission', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'requestPermission', + result: PlatformException( + code: "PERMISSION_REQUEST_IN_PROGRESS", + message: "Permissions already being requested.", + details: null, + ), ), - ), - ], - ); + ], + ); - // Act - final permissionFuture = GeolocatorAndroid().requestPermission(); + // Act + final permissionFuture = GeolocatorAndroid().requestPermission(); - // Assert - expect( - permissionFuture, - throwsA( - isA().having( - (e) => e.message, - 'description', - 'Permissions already being requested.', + // Assert + expect( + permissionFuture, + throwsA( + isA().having( + (e) => e.message, + 'description', + 'Permissions already being requested.', + ), ), - ), - ); - }); + ); + }, + ); - test('Should receive an exception when permission definitions not found', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'requestPermission', - result: PlatformException( - code: 'PERMISSION_DEFINITIONS_NOT_FOUND', - message: 'Permission definitions are not found.', - details: null, + test( + 'Should receive an exception when permission definitions not found', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'requestPermission', + result: PlatformException( + code: 'PERMISSION_DEFINITIONS_NOT_FOUND', + message: 'Permission definitions are not found.', + details: null, + ), ), - ), - ], - ); + ], + ); - // Act - final permissionFuture = GeolocatorAndroid().requestPermission(); + // Act + final permissionFuture = GeolocatorAndroid().requestPermission(); - // Assert - expect( - permissionFuture, - throwsA( - isA().having( - (e) => e.message, - 'description', - 'Permission definitions are not found.', + // Assert + expect( + permissionFuture, + throwsA( + isA().having( + (e) => e.message, + 'description', + 'Permission definitions are not found.', + ), ), - ), - ); - }); + ); + }, + ); - test('Should receive an exception when android activity is missing', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'requestPermission', - result: PlatformException( - code: 'ACTIVITY_MISSING', - message: 'Activity is missing.', - details: null, + test( + 'Should receive an exception when android activity is missing', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'requestPermission', + result: PlatformException( + code: 'ACTIVITY_MISSING', + message: 'Activity is missing.', + details: null, + ), ), - ), - ], - ); + ], + ); - // Act - final permissionFuture = GeolocatorAndroid().requestPermission(); + // Act + final permissionFuture = GeolocatorAndroid().requestPermission(); - // Assert - expect( - permissionFuture, - throwsA( - isA().having( - (e) => e.message, - 'description', - 'Activity is missing.', + // Assert + expect( + permissionFuture, + throwsA( + isA().having( + (e) => e.message, + 'description', + 'Activity is missing.', + ), ), - ), - ); - }); + ); + }, + ); }); - group('isLocationServiceEnabled: When checking the location service status', - () { - test('Should receive true if location services are enabled', () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: const [ - MethodMock( - methodName: 'isLocationServiceEnabled', - result: true, - ), - ], - ); + group( + 'isLocationServiceEnabled: When checking the location service status', + () { + test('Should receive true if location services are enabled', () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: const [ + MethodMock(methodName: 'isLocationServiceEnabled', result: true), + ], + ); - // Act - final isLocationServiceEnabled = - await GeolocatorAndroid().isLocationServiceEnabled(); + // Act + final isLocationServiceEnabled = + await GeolocatorAndroid().isLocationServiceEnabled(); - // Assert - expect( - isLocationServiceEnabled, - true, - ); - }); + // Assert + expect(isLocationServiceEnabled, true); + }); - test('Should receive false if location services are disabled', () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: const [ - MethodMock( - methodName: 'isLocationServiceEnabled', - result: false, - ), - ], - ); + test( + 'Should receive false if location services are disabled', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: const [ + MethodMock( + methodName: 'isLocationServiceEnabled', + result: false, + ), + ], + ); - // Act - final isLocationServiceEnabled = - await GeolocatorAndroid().isLocationServiceEnabled(); + // Act + final isLocationServiceEnabled = + await GeolocatorAndroid().isLocationServiceEnabled(); - // Assert - expect( - isLocationServiceEnabled, - false, + // Assert + expect(isLocationServiceEnabled, false); + }, ); - }); - }); + }, + ); group('getLastKnownPosition: When requesting the last know position', () { test('Should receive a position if permissions are granted', () async { @@ -568,10 +547,7 @@ void main() { // Arrange expect(position, mockPosition); expect(methodChannel.log, [ - isMethodCall( - 'getLastKnownPosition', - arguments: expectedArguments, - ), + isMethodCall('getLastKnownPosition', arguments: expectedArguments), ]); }); @@ -624,8 +600,9 @@ void main() { ); const requestId = 'requestId'; - const locationSettings = - LocationSettings(accuracy: LocationAccuracy.low); + const locationSettings = LocationSettings( + accuracy: LocationAccuracy.low, + ); final expectedArguments = { ...locationSettings.toJson(), @@ -641,10 +618,7 @@ void main() { // Assert expect(position, mockPosition); expect(channel.log, [ - isMethodCall( - 'getCurrentPosition', - arguments: expectedArguments, - ), + isMethodCall('getCurrentPosition', arguments: expectedArguments), ]); }); @@ -663,10 +637,12 @@ void main() { const requestIdFirst = 'requestIdFirst'; const requestIdSecond = 'requestIdSecond'; - const locationSettingsFirst = - LocationSettings(accuracy: LocationAccuracy.low); - const locationSettingsSecond = - LocationSettings(accuracy: LocationAccuracy.high); + const locationSettingsFirst = LocationSettings( + accuracy: LocationAccuracy.low, + ); + const locationSettingsSecond = LocationSettings( + accuracy: LocationAccuracy.high, + ); final expectedFirstArguments = { ...locationSettingsFirst.toJson(), @@ -692,10 +668,7 @@ void main() { expect(firstPosition, mockPosition); expect(secondPosition, mockPosition); expect(channel.log, [ - isMethodCall( - 'getCurrentPosition', - arguments: expectedFirstArguments, - ), + isMethodCall('getCurrentPosition', arguments: expectedFirstArguments), isMethodCall( 'getCurrentPosition', arguments: expectedSecondArguments, @@ -703,97 +676,97 @@ void main() { ]); }); - test('Should throw a permission denied exception if permission is denied', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'getCurrentPosition', - result: PlatformException( - code: 'PERMISSION_DENIED', - message: 'Permission denied', - details: null, + test( + 'Should throw a permission denied exception if permission is denied', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'getCurrentPosition', + result: PlatformException( + code: 'PERMISSION_DENIED', + message: 'Permission denied', + details: null, + ), ), - ), - ], - ); + ], + ); - // Act - final future = GeolocatorAndroid().getCurrentPosition(); + // Act + final future = GeolocatorAndroid().getCurrentPosition(); - // Assert - expect( - future, - throwsA( - isA().having( - (e) => e.message, - 'message', - 'Permission denied', + // Assert + expect( + future, + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Permission denied', + ), ), - ), - ); - }); + ); + }, + ); test( - // ignore: lines_longer_than_80_chars - 'Should throw a location service disabled exception if location services are disabled', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'getCurrentPosition', - result: PlatformException( - code: 'LOCATION_SERVICES_DISABLED', - message: '', - details: null, + // ignore: lines_longer_than_80_chars + 'Should throw a location service disabled exception if location services are disabled', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'getCurrentPosition', + result: PlatformException( + code: 'LOCATION_SERVICES_DISABLED', + message: '', + details: null, + ), ), - ), - ], - ); + ], + ); - // Act - final future = GeolocatorAndroid().getCurrentPosition(); + // Act + final future = GeolocatorAndroid().getCurrentPosition(); - // Assert - expect( - future, - throwsA(isA()), - ); - }); - - test('Should throw a timeout exception when timeLimit is reached', - () async { - // Arrange - MethodChannelMock( - channelName: 'flutter.baseflow.com/geolocator_android', - methods: [ - MethodMock( - methodName: 'getCurrentPosition', - delay: const Duration(milliseconds: 10), - result: mockPosition.toJson(), - ), - const MethodMock( - methodName: 'cancelGetCurrentPosition', - ), - ], - ); + // Assert + expect(future, throwsA(isA())); + }, + ); - try { - await GeolocatorAndroid().getCurrentPosition( - locationSettings: const LocationSettings( - timeLimit: Duration(milliseconds: 5), - ), + test( + 'Should throw a timeout exception when timeLimit is reached', + () async { + // Arrange + MethodChannelMock( + channelName: 'flutter.baseflow.com/geolocator_android', + methods: [ + MethodMock( + methodName: 'getCurrentPosition', + delay: const Duration(milliseconds: 10), + result: mockPosition.toJson(), + ), + const MethodMock(methodName: 'cancelGetCurrentPosition'), + ], ); - fail('Expected a TimeoutException and should not reach here.'); - } on TimeoutException catch (e) { - expect(e, isA()); - } - }); + try { + await GeolocatorAndroid().getCurrentPosition( + locationSettings: const LocationSettings( + timeLimit: Duration(milliseconds: 5), + ), + ); + + fail('Expected a TimeoutException and should not reach here.'); + } on TimeoutException catch (e) { + expect(e, isA()); + } + }, + ); test('Should cancel location stream when timeLimit is reached', () async { // Arrange @@ -805,9 +778,7 @@ void main() { methodName: 'getCurrentPosition', delay: Duration(milliseconds: 10), ), - MethodMock( - methodName: cancelMethodName, - ), + MethodMock(methodName: cancelMethodName), ], ); @@ -826,54 +797,52 @@ void main() { }); }); - group('getPositionStream: When requesting a stream of position updates', - () { + group('getPositionStream: When requesting a stream of position updates', () { group('And requesting for position update multiple times', () { test('Should return the same stream', () { final plugin = GeolocatorAndroid(); final firstStream = plugin.getPositionStream(); final secondStream = plugin.getPositionStream(); - expect( - identical(firstStream, secondStream), - true, - ); + expect(identical(firstStream, secondStream), true); }); - test('Should return a new stream when all subscriptions are cancelled', - () { - final plugin = GeolocatorAndroid(); + test( + 'Should return a new stream when all subscriptions are cancelled', + () { + final plugin = GeolocatorAndroid(); - // Get two position streams - final firstStream = plugin.getPositionStream(); - final secondStream = plugin.getPositionStream(); + // Get two position streams + final firstStream = plugin.getPositionStream(); + final secondStream = plugin.getPositionStream(); - // Streams are the same object - expect(firstStream == secondStream, true); + // Streams are the same object + expect(firstStream == secondStream, true); - // Add multiple subscriptions - StreamSubscription? firstSubscription = - firstStream.listen((event) {}); - StreamSubscription? secondSubscription = - secondStream.listen((event) {}); + // Add multiple subscriptions + StreamSubscription? firstSubscription = firstStream + .listen((event) {}); + StreamSubscription? secondSubscription = secondStream + .listen((event) {}); - // Cancel first subscription - firstSubscription.cancel(); - firstSubscription = null; + // Cancel first subscription + firstSubscription.cancel(); + firstSubscription = null; - // Stream is still the same as the first one - final cachedStream = plugin.getPositionStream(); - expect(firstStream == cachedStream, true); + // Stream is still the same as the first one + final cachedStream = plugin.getPositionStream(); + expect(firstStream == cachedStream, true); - // Cancel second subscription - secondSubscription.cancel(); - secondSubscription = null; + // Cancel second subscription + secondSubscription.cancel(); + secondSubscription = null; - // After all listeners have been removed, the next stream - // retrieved is a new one. - final thirdStream = plugin.getPositionStream(); - expect(firstStream != thirdStream, true); - }); + // After all listeners have been removed, the next stream + // retrieved is a new one. + final thirdStream = plugin.getPositionStream(); + expect(firstStream != thirdStream, true); + }, + ); }); test('PositionStream can be listened to and can be canceled', () { @@ -886,9 +855,11 @@ void main() { ); var stream = GeolocatorAndroid().getPositionStream( - locationSettings: AndroidSettings(useMSLAltitude: false)); - StreamSubscription? streamSubscription = - stream.listen((event) {}); + locationSettings: AndroidSettings(useMSLAltitude: false), + ); + StreamSubscription? streamSubscription = stream.listen( + (event) {}, + ); streamSubscription.pause(); expect(streamSubscription.isPaused, true); @@ -899,91 +870,102 @@ void main() { }); test( - // ignore: lines_longer_than_80_chars - 'Should correctly handle done event', () async { - // Arrange - final completer = Completer(); - completer.future.timeout(const Duration(milliseconds: 50), - onTimeout: () => - fail('getPositionStream should trigger done and not timeout.')); - final streamController = - StreamController>.broadcast(); - EventChannelMock( - channelName: 'flutter.baseflow.com/geolocator_updates_android', - stream: streamController.stream, - ); + // ignore: lines_longer_than_80_chars + 'Should correctly handle done event', + () async { + // Arrange + final completer = Completer(); + completer.future.timeout( + const Duration(milliseconds: 50), + onTimeout: + () => fail( + 'getPositionStream should trigger done and not timeout.', + ), + ); + final streamController = + StreamController>.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/geolocator_updates_android', + stream: streamController.stream, + ); - // Act - GeolocatorAndroid().getPositionStream().listen( - (event) {}, - onDone: completer.complete, - ); + // Act + GeolocatorAndroid().getPositionStream().listen( + (event) {}, + onDone: completer.complete, + ); - await streamController.close(); + await streamController.close(); - //Assert - await completer.future; - }); + //Assert + await completer.future; + }, + ); test( - // ignore: lines_longer_than_80_chars - 'Should receive a stream with position updates if permissions are granted', - () async { - // Arrange - final streamController = - StreamController>.broadcast(); - EventChannelMock( - channelName: 'flutter.baseflow.com/geolocator_updates_android', - stream: streamController.stream, - ); + // ignore: lines_longer_than_80_chars + 'Should receive a stream with position updates if permissions are granted', + () async { + // Arrange + final streamController = + StreamController>.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/geolocator_updates_android', + stream: streamController.stream, + ); - // Act - final positionStream = GeolocatorAndroid().getPositionStream( - locationSettings: AndroidSettings(useMSLAltitude: false)); - final streamQueue = StreamQueue(positionStream); + // Act + final positionStream = GeolocatorAndroid().getPositionStream( + locationSettings: AndroidSettings(useMSLAltitude: false), + ); + final streamQueue = StreamQueue(positionStream); - // Emit test events - streamController.add(mockPosition.toJson()); - streamController.add(mockPosition.toJson()); - streamController.add(mockPosition.toJson()); + // Emit test events + streamController.add(mockPosition.toJson()); + streamController.add(mockPosition.toJson()); + streamController.add(mockPosition.toJson()); - // Assert - expect(await streamQueue.next, mockPosition); - expect(await streamQueue.next, mockPosition); - expect(await streamQueue.next, mockPosition); + // Assert + expect(await streamQueue.next, mockPosition); + expect(await streamQueue.next, mockPosition); + expect(await streamQueue.next, mockPosition); - // Clean up - await streamQueue.cancel(); - await streamController.close(); - }); + // Clean up + await streamQueue.cancel(); + await streamController.close(); + }, + ); test( - // ignore: lines_longer_than_80_chars - 'Should continue listening to the stream when exception is thrown ', - () async { - // Arrange - final streamController = - StreamController>.broadcast(); - EventChannelMock( - channelName: 'flutter.baseflow.com/geolocator_updates_android', - stream: streamController.stream, - ); - - // Act - final positionStream = GeolocatorAndroid().getPositionStream(); - final streamQueue = StreamQueue(positionStream); + // ignore: lines_longer_than_80_chars + 'Should continue listening to the stream when exception is thrown ', + () async { + // Arrange + final streamController = + StreamController>.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/geolocator_updates_android', + stream: streamController.stream, + ); - // Emit test events - streamController.add(mockPosition.toJson()); - streamController.addError(PlatformException( - code: 'PERMISSION_DENIED', - message: 'Permission denied', - details: null)); - streamController.add(mockPosition.toJson()); + // Act + final positionStream = GeolocatorAndroid().getPositionStream(); + final streamQueue = StreamQueue(positionStream); + + // Emit test events + streamController.add(mockPosition.toJson()); + streamController.addError( + PlatformException( + code: 'PERMISSION_DENIED', + message: 'Permission denied', + details: null, + ), + ); + streamController.add(mockPosition.toJson()); - // Assert - expect(await streamQueue.next, mockPosition); - expect( + // Assert + expect(await streamQueue.next, mockPosition); + expect( streamQueue.next, throwsA( isA().having( @@ -991,38 +973,43 @@ void main() { 'message', 'Permission denied', ), - )); - expect(await streamQueue.next, mockPosition); + ), + ); + expect(await streamQueue.next, mockPosition); - // Clean up - await streamQueue.cancel(); - await streamController.close(); - }); + // 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/geolocator_updates_android', - stream: streamController.stream, - ); + // 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/geolocator_updates_android', + stream: streamController.stream, + ); - // Act - final positionStream = GeolocatorAndroid().getPositionStream(); - final streamQueue = StreamQueue(positionStream); + // Act + final positionStream = GeolocatorAndroid().getPositionStream(); + final streamQueue = StreamQueue(positionStream); - // Emit test error - streamController.addError(PlatformException( - code: 'PERMISSION_DENIED', - message: 'Permission denied', - details: null)); + // Emit test error + streamController.addError( + PlatformException( + code: 'PERMISSION_DENIED', + message: 'Permission denied', + details: null, + ), + ); - // Assert - expect( + // Assert + expect( streamQueue.next, throwsA( isA().having( @@ -1030,176 +1017,189 @@ void main() { 'message', 'Permission denied', ), - )); + ), + ); - // Clean up - streamQueue.cancel(); - streamController.close(); - }); + // 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/geolocator_updates_android', - stream: streamController.stream, - ); + // 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/geolocator_updates_android', + stream: streamController.stream, + ); - // Act - final positionStream = GeolocatorAndroid().getPositionStream(); - final streamQueue = StreamQueue(positionStream); + // Act + final positionStream = GeolocatorAndroid().getPositionStream(); + final streamQueue = StreamQueue(positionStream); - // Emit test error - streamController.addError(PlatformException( - code: 'LOCATION_SERVICES_DISABLED', - message: 'Location services disabled', - details: null)); + // Emit test error + streamController.addError( + PlatformException( + code: 'LOCATION_SERVICES_DISABLED', + message: 'Location services disabled', + details: null, + ), + ); - // Assert - expect( + // Assert + expect( streamQueue.next, - throwsA( - isA(), - )); + throwsA(isA()), + ); - // Clean up - streamQueue.cancel(); - streamController.close(); - }); + // 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/geolocator_updates_android', - stream: streamController.stream, - ); + // ignore: lines_longer_than_80_chars + 'Should receive a already subscribed exception', + () async { + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/geolocator_updates_android', + stream: streamController.stream, + ); - // Act - final positionStream = GeolocatorAndroid().getPositionStream(); - final streamQueue = StreamQueue(positionStream); + // Act + final positionStream = GeolocatorAndroid().getPositionStream(); + final streamQueue = StreamQueue(positionStream); - // Emit test error - streamController.addError(PlatformException( - code: 'PERMISSION_REQUEST_IN_PROGRESS', - message: 'A permission request is already in progress', - details: null)); + // Emit test error + streamController.addError( + PlatformException( + code: 'PERMISSION_REQUEST_IN_PROGRESS', + message: 'A permission request is already in progress', + details: null, + ), + ); - // Assert - expect( + // Assert + expect( streamQueue.next, - throwsA( - isA(), - )); + throwsA(isA()), + ); - // Clean up - streamQueue.cancel(); - streamController.close(); - }); + // 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/geolocator_updates_android', - stream: streamController.stream, - ); + // ignore: lines_longer_than_80_chars + 'Should receive a already subscribed exception', + () async { + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/geolocator_updates_android', + stream: streamController.stream, + ); - // Act - final positionStream = GeolocatorAndroid().getPositionStream(); - final streamQueue = StreamQueue(positionStream); + // Act + final positionStream = GeolocatorAndroid().getPositionStream(); + final streamQueue = StreamQueue(positionStream); - // Emit test error - streamController.addError(PlatformException( - code: 'LOCATION_SUBSCRIPTION_ACTIVE', - message: 'Already subscribed to receive a position stream', - details: null)); + // 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(), - )); + // Assert + expect(streamQueue.next, throwsA(isA())); - // Clean up - streamQueue.cancel(); - streamController.close(); - }); + // 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/geolocator_updates_android', - stream: streamController.stream, - ); + // ignore: lines_longer_than_80_chars + 'Should receive a position update exception', + () async { + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: 'flutter.baseflow.com/geolocator_updates_android', + stream: streamController.stream, + ); - // Act - final positionStream = GeolocatorAndroid().getPositionStream(); - final streamQueue = StreamQueue(positionStream); + // Act + final positionStream = GeolocatorAndroid().getPositionStream(); + final streamQueue = StreamQueue(positionStream); - // Emit test error - streamController.addError(PlatformException( - code: 'LOCATION_UPDATE_FAILURE', - message: 'A permission request is already in progress', - details: null)); + // 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(), - )); + // Assert + expect(streamQueue.next, throwsA(isA())); - // Clean up - streamQueue.cancel(); - streamController.close(); - }); + // Clean up + streamQueue.cancel(); + streamController.close(); + }, + ); - test('Should throw a timeout exception when timeLimit is reached', - () async { - // Arrange - final streamController = StreamController>(); - EventChannelMock( - channelName: 'flutter.baseflow.com/geolocator_updates_android', - stream: streamController.stream, - ); - const expectedArguments = LocationSettings( - accuracy: LocationAccuracy.low, - distanceFilter: 0, - ); + test( + 'Should throw a timeout exception when timeLimit is reached', + () async { + // Arrange + final streamController = StreamController>(); + EventChannelMock( + channelName: 'flutter.baseflow.com/geolocator_updates_android', + stream: streamController.stream, + ); + const expectedArguments = LocationSettings( + accuracy: LocationAccuracy.low, + distanceFilter: 0, + ); - // Act - final positionStream = GeolocatorAndroid().getPositionStream( - locationSettings: LocationSettings( - accuracy: expectedArguments.accuracy, - timeLimit: const Duration(milliseconds: 5), - ), - ); - final streamQueue = StreamQueue(positionStream); + // Act + final positionStream = GeolocatorAndroid().getPositionStream( + locationSettings: LocationSettings( + accuracy: expectedArguments.accuracy, + timeLimit: const Duration(milliseconds: 5), + ), + ); + final streamQueue = StreamQueue(positionStream); - streamController.add(mockPosition.toJson()); + streamController.add(mockPosition.toJson()); - await Future.delayed(const Duration(milliseconds: 5)); + await Future.delayed(const Duration(milliseconds: 5)); - // Assert - expect(await streamQueue.next, mockPosition); - expect(streamQueue.next, throwsA(isA())); - }); + // Assert + expect(await streamQueue.next, mockPosition); + expect(streamQueue.next, throwsA(isA())); + }, + ); test('Should cleanup the previous stream on timeout exception', () async { // Arrange @@ -1249,91 +1249,98 @@ void main() { }); group( - // ignore: lines_longer_than_80_chars - 'getServiceStream: When requesting a stream of location service status updates', - () { - group('And requesting for location service status updates multiple times', + // ignore: lines_longer_than_80_chars + 'getServiceStream: When requesting a stream of location service status updates', + () { + group( + 'And requesting for location service status updates multiple times', () { - test('Should return the same stream', () { - final plugin = GeolocatorAndroid(); - final firstStream = plugin.getServiceStatusStream(); - final secondstream = plugin.getServiceStatusStream(); + test('Should return the same stream', () { + final plugin = GeolocatorAndroid(); + final firstStream = plugin.getServiceStatusStream(); + final secondstream = plugin.getServiceStatusStream(); - expect( - identical(firstStream, secondstream), - true, - ); - }); - }); + expect(identical(firstStream, secondstream), true); + }); + }, + ); - test( + test( // ignore: lines_longer_than_80_chars 'Should receive a stream with location service updates if permissions are granted', () async { - // Arrange - final streamController = StreamController.broadcast(); - EventChannelMock( - channelName: - 'flutter.baseflow.com/geolocator_service_updates_android', - stream: streamController.stream); + // Arrange + final streamController = StreamController.broadcast(); + EventChannelMock( + channelName: + 'flutter.baseflow.com/geolocator_service_updates_android', + stream: streamController.stream, + ); - // Act - final locationServiceStream = - GeolocatorAndroid().getServiceStatusStream(); - final streamQueue = StreamQueue(locationServiceStream); + // Act + final locationServiceStream = + GeolocatorAndroid().getServiceStatusStream(); + final streamQueue = StreamQueue(locationServiceStream); - // Emit test events - streamController.add(0); // disabled value in native enum - streamController.add(1); // enabled value in native enum + // Emit test events + streamController.add(0); // disabled value in native enum + streamController.add(1); // enabled value in native enum - //Assert - expect(await streamQueue.next, ServiceStatus.disabled); - expect(await streamQueue.next, ServiceStatus.enabled); + //Assert + expect(await streamQueue.next, ServiceStatus.disabled); + expect(await streamQueue.next, ServiceStatus.enabled); - // Clean up - await streamQueue.cancel(); - await streamController.close(); - }); + // Clean up + await streamQueue.cancel(); + await streamController.close(); + }, + ); - test( + test( // ignore: lines_longer_than_80_chars 'Should receive an exception if android activity is missing', () async { - // Arrange - final streamController = - StreamController.broadcast(); - EventChannelMock( - channelName: - 'flutter.baseflow.com/geolocator_service_updates_android', - stream: streamController.stream, - ); + // Arrange + final streamController = + StreamController.broadcast(); + EventChannelMock( + channelName: + 'flutter.baseflow.com/geolocator_service_updates_android', + stream: streamController.stream, + ); - // Act - final positionStream = GeolocatorAndroid().getServiceStatusStream(); - final streamQueue = StreamQueue(positionStream); + // Act + final positionStream = GeolocatorAndroid().getServiceStatusStream(); + final streamQueue = StreamQueue(positionStream); - // Emit test error - streamController.addError(PlatformException( - code: 'ACTIVITY_MISSING', - message: 'Activity missing', - details: null)); + // Emit test error + streamController.addError( + PlatformException( + code: 'ACTIVITY_MISSING', + message: 'Activity missing', + details: null, + ), + ); - // Assert - expect( - streamQueue.next, - throwsA( - isA().having( - (e) => e.message, - 'message', - 'Activity missing', + // Assert + expect( + streamQueue.next, + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Activity missing', + ), ), - )); + ); - // Clean up - streamQueue.cancel(); - streamController.close(); - }); - }); + // Clean up + streamQueue.cancel(); + streamController.close(); + }, + ); + }, + ); group('openAppSettings: When opening the App settings', () { test('Should receive true if the page can be opened', () async { @@ -1341,10 +1348,7 @@ void main() { MethodChannelMock( channelName: 'flutter.baseflow.com/geolocator_android', methods: const [ - MethodMock( - methodName: 'openAppSettings', - result: true, - ), + MethodMock(methodName: 'openAppSettings', result: true), ], ); @@ -1353,10 +1357,7 @@ void main() { await GeolocatorAndroid().openAppSettings(); // Assert - expect( - hasOpenedAppSettings, - true, - ); + expect(hasOpenedAppSettings, true); }); test('Should receive false if an error occurred', () async { @@ -1364,10 +1365,7 @@ void main() { MethodChannelMock( channelName: 'flutter.baseflow.com/geolocator_android', methods: const [ - MethodMock( - methodName: 'openAppSettings', - result: false, - ), + MethodMock(methodName: 'openAppSettings', result: false), ], ); @@ -1376,10 +1374,7 @@ void main() { await GeolocatorAndroid().openAppSettings(); // Assert - expect( - hasOpenedAppSettings, - false, - ); + expect(hasOpenedAppSettings, false); }); }); @@ -1389,10 +1384,7 @@ void main() { MethodChannelMock( channelName: 'flutter.baseflow.com/geolocator_android', methods: const [ - MethodMock( - methodName: 'openLocationSettings', - result: true, - ), + MethodMock(methodName: 'openLocationSettings', result: true), ], ); @@ -1401,10 +1393,7 @@ void main() { await GeolocatorAndroid().openLocationSettings(); // Assert - expect( - hasOpenedLocationSettings, - true, - ); + expect(hasOpenedLocationSettings, true); }); test('Should receive false if an error occurred', () async { @@ -1412,10 +1401,7 @@ void main() { MethodChannelMock( channelName: 'flutter.baseflow.com/geolocator_android', methods: const [ - MethodMock( - methodName: 'openLocationSettings', - result: false, - ), + MethodMock(methodName: 'openLocationSettings', result: false), ], ); @@ -1424,106 +1410,92 @@ void main() { await GeolocatorAndroid().openLocationSettings(); // Assert - expect( - hasOpenedLocationSettings, - false, - ); + expect(hasOpenedLocationSettings, false); }); }); group('jsonSerialization: When serializing to json', () { - test('Should produce valid map with all the settings when calling toJson', - () async { - // Arrange - final settings = AndroidSettings( - accuracy: LocationAccuracy.best, - distanceFilter: 5, - forceLocationManager: false, - intervalDuration: const Duration(seconds: 1), - timeLimit: const Duration(seconds: 1), - useMSLAltitude: false, - foregroundNotificationConfig: const ForegroundNotificationConfig( - color: Colors.amber, - enableWakeLock: false, - enableWifiLock: false, - notificationIcon: AndroidResource( - name: 'name', - defType: 'defType', + test( + 'Should produce valid map with all the settings when calling toJson', + () async { + // Arrange + final settings = AndroidSettings( + accuracy: LocationAccuracy.best, + distanceFilter: 5, + forceLocationManager: false, + intervalDuration: const Duration(seconds: 1), + timeLimit: const Duration(seconds: 1), + useMSLAltitude: false, + foregroundNotificationConfig: const ForegroundNotificationConfig( + color: Colors.amber, + enableWakeLock: false, + enableWifiLock: false, + notificationIcon: AndroidResource( + name: 'name', + defType: 'defType', + ), + notificationText: 'text', + notificationTitle: 'title', + setOngoing: true, ), - notificationText: 'text', - notificationTitle: 'title', - setOngoing: true, - ), - ); + ); - // Act - final jsonMap = settings.toJson(); + // Act + final jsonMap = settings.toJson(); - // Assert - expect( - jsonMap['accuracy'], - settings.accuracy.index, - ); - expect( - jsonMap['distanceFilter'], - settings.distanceFilter, - ); - expect( - jsonMap['forceLocationManager'], - settings.forceLocationManager, - ); - expect( - jsonMap['timeInterval'], - settings.intervalDuration!.inMilliseconds, - ); - expect( - jsonMap['useMSLAltitude'], - settings.useMSLAltitude, - ); - expect( - jsonMap['foregroundNotificationConfig']['enableWakeLock'], - settings.foregroundNotificationConfig!.enableWakeLock, - ); - expect( - jsonMap['foregroundNotificationConfig']['enableWifiLock'], - settings.foregroundNotificationConfig!.enableWifiLock, - ); - expect( - jsonMap['foregroundNotificationConfig']['notificationIcon']['name'], - settings.foregroundNotificationConfig!.notificationIcon.name, - ); - expect( - jsonMap['foregroundNotificationConfig']['notificationIcon'] - ['defType'], - settings.foregroundNotificationConfig!.notificationIcon.defType, - ); - expect( - jsonMap['foregroundNotificationConfig']['notificationText'], - settings.foregroundNotificationConfig!.notificationText, - ); - expect( - jsonMap['foregroundNotificationConfig']['notificationTitle'], - settings.foregroundNotificationConfig!.notificationTitle, - ); - expect( - jsonMap['foregroundNotificationConfig']['setOngoing'], - settings.foregroundNotificationConfig!.setOngoing, - ); - expect( - jsonMap['foregroundNotificationConfig']['color'], - settings.foregroundNotificationConfig!.color!.toARGB32, - ); - }); + // Assert + expect(jsonMap['accuracy'], settings.accuracy.index); + expect(jsonMap['distanceFilter'], settings.distanceFilter); + expect( + jsonMap['forceLocationManager'], + settings.forceLocationManager, + ); + expect( + jsonMap['timeInterval'], + settings.intervalDuration!.inMilliseconds, + ); + expect(jsonMap['useMSLAltitude'], settings.useMSLAltitude); + expect( + jsonMap['foregroundNotificationConfig']['enableWakeLock'], + settings.foregroundNotificationConfig!.enableWakeLock, + ); + expect( + jsonMap['foregroundNotificationConfig']['enableWifiLock'], + settings.foregroundNotificationConfig!.enableWifiLock, + ); + expect( + jsonMap['foregroundNotificationConfig']['notificationIcon']['name'], + settings.foregroundNotificationConfig!.notificationIcon.name, + ); + expect( + jsonMap['foregroundNotificationConfig']['notificationIcon']['defType'], + settings.foregroundNotificationConfig!.notificationIcon.defType, + ); + expect( + jsonMap['foregroundNotificationConfig']['notificationText'], + settings.foregroundNotificationConfig!.notificationText, + ); + expect( + jsonMap['foregroundNotificationConfig']['notificationTitle'], + settings.foregroundNotificationConfig!.notificationTitle, + ); + expect( + jsonMap['foregroundNotificationConfig']['setOngoing'], + settings.foregroundNotificationConfig!.setOngoing, + ); + expect( + jsonMap['foregroundNotificationConfig']['color'], + settings.foregroundNotificationConfig!.color!.toARGB32, + ); + }, + ); test('Should receive false if an error occurred', () async { // Arrange MethodChannelMock( channelName: 'flutter.baseflow.com/geolocator_android', methods: const [ - MethodMock( - methodName: 'openLocationSettings', - result: false, - ), + MethodMock(methodName: 'openLocationSettings', result: false), ], ); @@ -1532,10 +1504,7 @@ void main() { await GeolocatorAndroid().openLocationSettings(); // Assert - expect( - hasOpenedLocationSettings, - false, - ); + expect(hasOpenedLocationSettings, false); }); }); }); diff --git a/geolocator_android/test/method_channel_mock.dart b/geolocator_android/test/method_channel_mock.dart index 1043a5ceb..2419a86d9 100644 --- a/geolocator_android/test/method_channel_mock.dart +++ b/geolocator_android/test/method_channel_mock.dart @@ -18,10 +18,8 @@ class MethodChannelMock { final MethodChannel methodChannel; final log = []; - MethodChannelMock({ - required String channelName, - required this.methods, - }) : methodChannel = MethodChannel(channelName) { + MethodChannelMock({required String channelName, required this.methods}) + : methodChannel = MethodChannel(channelName) { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(methodChannel, _handler); } @@ -31,10 +29,12 @@ class MethodChannelMock { final method = methods.firstWhere( (MethodMock methodMock) => methodMock.methodName == methodCall.method, - orElse: () => throw MissingPluginException( - 'No implementation found for ' - 'method ${methodCall.method} on channel ${methodChannel.name}', - ), + orElse: + () => + throw MissingPluginException( + 'No implementation found for ' + 'method ${methodCall.method} on channel ${methodChannel.name}', + ), ); return Future.delayed(method.delay, () { From deaa857f7e9b84fc51756c138c23b67317db17a0 Mon Sep 17 00:00:00 2001 From: Ahmed Makled <36972797+Ahmed-Makled@users.noreply.github.com> Date: Thu, 15 May 2025 02:10:00 +0300 Subject: [PATCH 4/5] Refactor code formatting in geolocator_android.dart and foreground_settings.dart for improved readability --- .../lib/src/geolocator_android.dart | 116 +++++++++--------- .../lib/src/types/foreground_settings.dart | 16 +-- 2 files changed, 64 insertions(+), 68 deletions(-) diff --git a/geolocator_android/lib/src/geolocator_android.dart b/geolocator_android/lib/src/geolocator_android.dart index 04c1ced86..206867c20 100644 --- a/geolocator_android/lib/src/geolocator_android.dart +++ b/geolocator_android/lib/src/geolocator_android.dart @@ -8,18 +8,21 @@ import 'package:uuid/uuid.dart'; /// An implementation of [GeolocatorPlatform] that uses method channels. class GeolocatorAndroid extends GeolocatorPlatform { /// The method channel used to interact with the native platform. - static const _methodChannel = - MethodChannel('flutter.baseflow.com/geolocator_android'); + static const _methodChannel = MethodChannel( + 'flutter.baseflow.com/geolocator_android', + ); /// The event channel used to receive [Position] updates from the native /// platform. - static const _eventChannel = - EventChannel('flutter.baseflow.com/geolocator_updates_android'); + static const _eventChannel = EventChannel( + 'flutter.baseflow.com/geolocator_updates_android', + ); /// The event channel used to receive [LocationServiceStatus] updates from the /// native platform. - static const _serviceStatusEventChannel = - EventChannel('flutter.baseflow.com/geolocator_service_updates_android'); + static const _serviceStatusEventChannel = EventChannel( + 'flutter.baseflow.com/geolocator_service_updates_android', + ); /// Registers this class as the default instance of [GeolocatorPlatform]. static void registerWith() { @@ -41,8 +44,9 @@ class GeolocatorAndroid extends GeolocatorPlatform { Future checkPermission() async { try { // ignore: omit_local_variable_types - final int permission = - await _methodChannel.invokeMethod('checkPermission'); + final int permission = await _methodChannel.invokeMethod( + 'checkPermission', + ); return permission.toLocationPermission(); } on PlatformException catch (e) { @@ -56,8 +60,9 @@ class GeolocatorAndroid extends GeolocatorPlatform { Future requestPermission() async { try { // ignore: omit_local_variable_types - final int permission = - await _methodChannel.invokeMethod('requestPermission'); + final int permission = await _methodChannel.invokeMethod( + 'requestPermission', + ); return permission.toLocationPermission(); } on PlatformException catch (e) { @@ -81,8 +86,10 @@ class GeolocatorAndroid extends GeolocatorPlatform { 'forceLocationManager': forceLocationManager, }; - final positionMap = - await _methodChannel.invokeMethod('getLastKnownPosition', parameters); + final positionMap = await _methodChannel.invokeMethod( + 'getLastKnownPosition', + parameters, + ); return positionMap != null ? AndroidPosition.fromMap(positionMap) : null; } on PlatformException catch (e) { @@ -94,8 +101,9 @@ class GeolocatorAndroid extends GeolocatorPlatform { @override Future getLocationAccuracy() async { - final int accuracy = - await _methodChannel.invokeMethod('getLocationAccuracy'); + final int accuracy = await _methodChannel.invokeMethod( + 'getLocationAccuracy', + ); return LocationAccuracyStatus.values[accuracy]; } @@ -111,13 +119,10 @@ class GeolocatorAndroid extends GeolocatorPlatform { final Duration? timeLimit = locationSettings?.timeLimit; - positionFuture = _methodChannel.invokeMethod( - 'getCurrentPosition', - { - ...?locationSettings?.toJson(), - 'requestId': requestId, - }, - ); + positionFuture = _methodChannel.invokeMethod('getCurrentPosition', { + ...?locationSettings?.toJson(), + 'requestId': requestId, + }); if (timeLimit != null) { positionFuture = positionFuture.timeout(timeLimit); @@ -126,13 +131,8 @@ class GeolocatorAndroid extends GeolocatorPlatform { final positionMap = await positionFuture; return AndroidPosition.fromMap(positionMap); } on TimeoutException { - final parameters = { - 'requestId': requestId, - }; - _methodChannel.invokeMethod( - 'cancelGetCurrentPosition', - parameters, - ); + final parameters = {'requestId': requestId}; + _methodChannel.invokeMethod('cancelGetCurrentPosition', parameters); rethrow; } on PlatformException catch (e) { final error = _handlePlatformException(e); @@ -152,20 +152,18 @@ class GeolocatorAndroid extends GeolocatorPlatform { _serviceStatusStream = serviceStatusStream .map((dynamic element) => ServiceStatus.values[element as int]) .handleError((error) { - _serviceStatusStream = null; - if (error is PlatformException) { - error = _handlePlatformException(error); - } - throw error; - }); + _serviceStatusStream = null; + if (error is PlatformException) { + error = _handlePlatformException(error); + } + throw error; + }); return _serviceStatusStream!; } @override - Stream getPositionStream({ - LocationSettings? locationSettings, - }) { + Stream getPositionStream({LocationSettings? locationSettings}) { if (_positionStream != null) { return _positionStream!; } @@ -181,34 +179,38 @@ class GeolocatorAndroid extends GeolocatorPlatform { timeLimit, onTimeout: (s) { _positionStream = null; - s.addError(TimeoutException( - 'Time limit reached while waiting for position update.', - timeLimit, - )); + s.addError( + TimeoutException( + 'Time limit reached while waiting for position update.', + timeLimit, + ), + ); s.close(); }, ); } _positionStream = positionStream - .map((dynamic element) => - AndroidPosition.fromMap(element.cast())) - .handleError( - (error) { - if (error is PlatformException) { - error = _handlePlatformException(error); - } - throw error; - }, - ); + .map( + (dynamic element) => + AndroidPosition.fromMap(element.cast()), + ) + .handleError((error) { + if (error is PlatformException) { + error = _handlePlatformException(error); + } + throw error; + }); return _positionStream!; } Stream _wrapStream(Stream incoming) { - return incoming.asBroadcastStream(onCancel: (subscription) { - subscription.cancel(); - _positionStream = null; - }); + return incoming.asBroadcastStream( + onCancel: (subscription) { + subscription.cancel(); + _positionStream = null; + }, + ); } @override @@ -218,9 +220,7 @@ class GeolocatorAndroid extends GeolocatorPlatform { try { final int status = await _methodChannel.invokeMethod( 'requestTemporaryFullAccuracy', - { - 'purposeKey': purposeKey, - }, + {'purposeKey': purposeKey}, ); return LocationAccuracyStatus.values[status]; } on PlatformException catch (e) { diff --git a/geolocator_android/lib/src/types/foreground_settings.dart b/geolocator_android/lib/src/types/foreground_settings.dart index 22983e09e..76f810bfc 100644 --- a/geolocator_android/lib/src/types/foreground_settings.dart +++ b/geolocator_android/lib/src/types/foreground_settings.dart @@ -11,17 +11,11 @@ class AndroidResource { final String defType; /// Uniquely identifies an Android resource. - const AndroidResource({ - required this.name, - this.defType = 'drawable', - }); + const AndroidResource({required this.name, this.defType = 'drawable'}); /// Returns a JSON representation of this class. Map toJson() { - return { - 'name': name, - 'defType': defType, - }; + return {'name': name, 'defType': defType}; } } @@ -53,8 +47,10 @@ class ForegroundNotificationConfig { required this.notificationTitle, required this.notificationText, this.notificationChannelName = 'Background Location', - this.notificationIcon = - const AndroidResource(name: 'ic_launcher', defType: 'mipmap'), + this.notificationIcon = const AndroidResource( + name: 'ic_launcher', + defType: 'mipmap', + ), this.enableWifiLock = false, this.enableWakeLock = false, this.setOngoing = false, From bc094047bacd0d9c14dee38bba1093d8d3889082 Mon Sep 17 00:00:00 2001 From: Ahmed Makled <36972797+Ahmed-Makled@users.noreply.github.com> Date: Thu, 15 May 2025 02:24:09 +0300 Subject: [PATCH 5/5] Fix Dart formatting issues for CI pipeline --- geolocator_android/example/lib/main.dart | 111 ++++++++---------- .../lib/src/geolocator_android.dart | 28 ++--- .../lib/src/types/android_position.dart | 36 +++--- .../lib/src/types/android_settings.dart | 15 +-- .../test/event_channel_mock.dart | 20 ++-- .../test/geolocator_android_test.dart | 64 +++++----- .../test/method_channel_mock.dart | 12 +- 7 files changed, 140 insertions(+), 146 deletions(-) diff --git a/geolocator_android/example/lib/main.dart b/geolocator_android/example/lib/main.dart index 0fccaf928..3b7b57e2e 100644 --- a/geolocator_android/example/lib/main.dart +++ b/geolocator_android/example/lib/main.dart @@ -9,8 +9,8 @@ import 'package:geolocator_platform_interface/geolocator_platform_interface.dart /// Defines the main theme color. final MaterialColor themeMaterialColor = BaseflowPluginExample.createMaterialColor( - const Color.fromRGBO(48, 49, 60, 1), - ); + const Color.fromRGBO(48, 49, 60, 1), +); void main() { runApp(const GeolocatorWidget()); @@ -78,22 +78,21 @@ class _GeolocatorWidgetState extends State { break; } }, - itemBuilder: - (context) => [ - const PopupMenuItem(value: 1, child: Text("Get Location Accuracy")), - if (Platform.isIOS) - const PopupMenuItem( - value: 2, - child: Text("Request Temporary Full Accuracy"), - ), - const PopupMenuItem(value: 3, child: Text("Open App Settings")), - if (Platform.isAndroid) - const PopupMenuItem( - value: 4, - child: Text("Open Location Settings"), - ), - const PopupMenuItem(value: 5, child: Text("Clear")), - ], + itemBuilder: (context) => [ + const PopupMenuItem(value: 1, child: Text("Get Location Accuracy")), + if (Platform.isIOS) + const PopupMenuItem( + value: 2, + child: Text("Request Temporary Full Accuracy"), + ), + const PopupMenuItem(value: 3, child: Text("Open App Settings")), + if (Platform.isAndroid) + const PopupMenuItem( + value: 4, + child: Text("Open Location Settings"), + ), + const PopupMenuItem(value: 5, child: Text("Clear")), + ], ); } @@ -146,18 +145,16 @@ class _GeolocatorWidgetState extends State { children: [ FloatingActionButton( onPressed: _toggleListening, - tooltip: - (_positionStreamSubscription == null) - ? 'Start position updates' - : _positionStreamSubscription!.isPaused + tooltip: (_positionStreamSubscription == null) + ? 'Start position updates' + : _positionStreamSubscription!.isPaused ? 'Resume' : 'Pause', backgroundColor: _determineButtonColor(), - child: - (_positionStreamSubscription == null || - _positionStreamSubscription!.isPaused) - ? const Icon(Icons.play_arrow) - : const Icon(Icons.pause), + child: (_positionStreamSubscription == null || + _positionStreamSubscription!.isPaused) + ? const Icon(Icons.play_arrow) + : const Icon(Icons.pause), ), sizedBox, FloatingActionButton( @@ -242,9 +239,8 @@ class _GeolocatorWidgetState extends State { setState(() {}); } - bool _isListening() => - !(_positionStreamSubscription == null || - _positionStreamSubscription!.isPaused); + bool _isListening() => !(_positionStreamSubscription == null || + _positionStreamSubscription!.isPaused); Color _determineButtonColor() { return _isListening() ? Colors.green : Colors.red; @@ -253,23 +249,22 @@ class _GeolocatorWidgetState extends State { void _toggleServiceStatusStream() { if (_serviceStatusStreamSubscription == null) { final serviceStatusStream = geolocatorAndroid.getServiceStatusStream(); - _serviceStatusStreamSubscription = serviceStatusStream - .handleError((error) { - _serviceStatusStreamSubscription?.cancel(); - _serviceStatusStreamSubscription = null; - }) - .listen((serviceStatus) { - String serviceStatusValue; - if (serviceStatus == ServiceStatus.enabled) { - serviceStatusValue = 'enabled'; - } else { - serviceStatusValue = 'disabled'; - } - _updatePositionList( - _PositionItemType.log, - 'Location service has been $serviceStatusValue', - ); - }); + _serviceStatusStreamSubscription = + serviceStatusStream.handleError((error) { + _serviceStatusStreamSubscription?.cancel(); + _serviceStatusStreamSubscription = null; + }).listen((serviceStatus) { + String serviceStatusValue; + if (serviceStatus == ServiceStatus.enabled) { + serviceStatusValue = 'enabled'; + } else { + serviceStatusValue = 'disabled'; + } + _updatePositionList( + _PositionItemType.log, + 'Location service has been $serviceStatusValue', + ); + }); } } @@ -302,18 +297,16 @@ class _GeolocatorWidgetState extends State { final positionStream = geolocatorAndroid.getPositionStream( locationSettings: androidSettings, ); - _positionStreamSubscription = positionStream - .handleError((error) { - _positionStreamSubscription?.cancel(); - _positionStreamSubscription = null; - }) - .listen((position) { - debugPrint(position.altitude.toString()); - _updatePositionList( - _PositionItemType.position, - position.toString(), - ); - }); + _positionStreamSubscription = positionStream.handleError((error) { + _positionStreamSubscription?.cancel(); + _positionStreamSubscription = null; + }).listen((position) { + debugPrint(position.altitude.toString()); + _updatePositionList( + _PositionItemType.position, + position.toString(), + ); + }); _positionStreamSubscription?.pause(); } diff --git a/geolocator_android/lib/src/geolocator_android.dart b/geolocator_android/lib/src/geolocator_android.dart index 206867c20..00dd4d11a 100644 --- a/geolocator_android/lib/src/geolocator_android.dart +++ b/geolocator_android/lib/src/geolocator_android.dart @@ -152,12 +152,12 @@ class GeolocatorAndroid extends GeolocatorPlatform { _serviceStatusStream = serviceStatusStream .map((dynamic element) => ServiceStatus.values[element as int]) .handleError((error) { - _serviceStatusStream = null; - if (error is PlatformException) { - error = _handlePlatformException(error); - } - throw error; - }); + _serviceStatusStream = null; + if (error is PlatformException) { + error = _handlePlatformException(error); + } + throw error; + }); return _serviceStatusStream!; } @@ -192,15 +192,15 @@ class GeolocatorAndroid extends GeolocatorPlatform { _positionStream = positionStream .map( - (dynamic element) => - AndroidPosition.fromMap(element.cast()), - ) + (dynamic element) => + AndroidPosition.fromMap(element.cast()), + ) .handleError((error) { - if (error is PlatformException) { - error = _handlePlatformException(error); - } - throw error; - }); + if (error is PlatformException) { + error = _handlePlatformException(error); + } + throw error; + }); return _positionStream!; } diff --git a/geolocator_android/lib/src/types/android_position.dart b/geolocator_android/lib/src/types/android_position.dart index 137e39d41..d44396b57 100644 --- a/geolocator_android/lib/src/types/android_position.dart +++ b/geolocator_android/lib/src/types/android_position.dart @@ -26,18 +26,18 @@ class AndroidPosition extends Position { super.floor, isMocked = false, }) : super( - longitude: longitude, - latitude: latitude, - timestamp: timestamp, - accuracy: accuracy, - altitude: altitude, - altitudeAccuracy: altitudeAccuracy, - heading: heading, - headingAccuracy: headingAccuracy, - speed: speed, - speedAccuracy: speedAccuracy, - isMocked: isMocked, - ); + longitude: longitude, + latitude: latitude, + timestamp: timestamp, + accuracy: accuracy, + altitude: altitude, + altitudeAccuracy: altitudeAccuracy, + heading: heading, + headingAccuracy: headingAccuracy, + speed: speed, + speedAccuracy: speedAccuracy, + isMocked: isMocked, + ); /// If available it returns the number of GNSS satellites. /// @@ -51,8 +51,7 @@ class AndroidPosition extends Position { @override bool operator ==(Object other) { - var areEqual = - other is AndroidPosition && + var areEqual = other is AndroidPosition && super == other && other.satelliteCount == satelliteCount && other.satellitesUsedInFix == satellitesUsedInFix; @@ -91,9 +90,10 @@ class AndroidPosition extends Position { /// serialized to JSON. @override Map toJson() { - return super.toJson()..addAll({ - 'gnss_satellite_count': satelliteCount, - 'gnss_satellites_used_in_fix': satellitesUsedInFix, - }); + return super.toJson() + ..addAll({ + 'gnss_satellite_count': satelliteCount, + 'gnss_satellites_used_in_fix': satellitesUsedInFix, + }); } } diff --git a/geolocator_android/lib/src/types/android_settings.dart b/geolocator_android/lib/src/types/android_settings.dart index 61eb766fe..cff08b510 100644 --- a/geolocator_android/lib/src/types/android_settings.dart +++ b/geolocator_android/lib/src/types/android_settings.dart @@ -90,12 +90,13 @@ class AndroidSettings extends LocationSettings { @override Map toJson() { - return super.toJson()..addAll({ - 'forceLocationManager': forceLocationManager, - 'timeInterval': intervalDuration?.inMilliseconds, - 'foregroundNotificationConfig': foregroundNotificationConfig?.toJson(), - 'useMSLAltitude': useMSLAltitude, - 'enableAccuracyFilter': enableAccuracyFilter, - }); + return super.toJson() + ..addAll({ + 'forceLocationManager': forceLocationManager, + 'timeInterval': intervalDuration?.inMilliseconds, + 'foregroundNotificationConfig': foregroundNotificationConfig?.toJson(), + 'useMSLAltitude': useMSLAltitude, + 'enableAccuracyFilter': enableAccuracyFilter, + }); } } diff --git a/geolocator_android/test/event_channel_mock.dart b/geolocator_android/test/event_channel_mock.dart index d45828784..9ef874de5 100644 --- a/geolocator_android/test/event_channel_mock.dart +++ b/geolocator_android/test/event_channel_mock.dart @@ -11,7 +11,7 @@ class EventChannelMock { StreamSubscription? _streamSubscription; EventChannelMock({required String channelName, required this.stream}) - : _methodChannel = MethodChannel(channelName) { + : _methodChannel = MethodChannel(channelName) { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(_methodChannel, _handler); } @@ -34,16 +34,14 @@ class EventChannelMock { } void _onListen() { - _streamSubscription = stream! - .handleError((e) { - _sendErrorEnvelope(e); - }) - .listen( - _sendSuccessEnvelope, - onDone: () { - _sendEnvelope(null); - }, - ); + _streamSubscription = stream!.handleError((e) { + _sendErrorEnvelope(e); + }).listen( + _sendSuccessEnvelope, + onDone: () { + _sendEnvelope(null); + }, + ); } void _onCancel() { diff --git a/geolocator_android/test/geolocator_android_test.dart b/geolocator_android/test/geolocator_android_test.dart index c3431fd7d..337fbe442 100644 --- a/geolocator_android/test/geolocator_android_test.dart +++ b/geolocator_android/test/geolocator_android_test.dart @@ -10,20 +10,20 @@ import 'event_channel_mock.dart'; import 'method_channel_mock.dart'; Position get mockPosition => AndroidPosition( - latitude: 52.561270, - longitude: 5.639382, - timestamp: DateTime.fromMillisecondsSinceEpoch(500, isUtc: true), - altitude: 3000.0, - altitudeAccuracy: 0.0, - satelliteCount: 2.0, - satellitesUsedInFix: 2.0, - accuracy: 0.0, - heading: 0.0, - headingAccuracy: 0.0, - speed: 0.0, - speedAccuracy: 0.0, - isMocked: false, -); + latitude: 52.561270, + longitude: 5.639382, + timestamp: DateTime.fromMillisecondsSinceEpoch(500, isUtc: true), + altitude: 3000.0, + altitudeAccuracy: 0.0, + satelliteCount: 2.0, + satellitesUsedInFix: 2.0, + accuracy: 0.0, + heading: 0.0, + headingAccuracy: 0.0, + speed: 0.0, + speedAccuracy: 0.0, + isMocked: false, + ); void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -153,9 +153,11 @@ void main() { ); }); - group('requestTemporaryFullAccuracy: When requesting temporary full' + group( + 'requestTemporaryFullAccuracy: When requesting temporary full' 'accuracy.', () { - test('Should receive reduced accuracy if Location Accuracy is pinned to' + test( + 'Should receive reduced accuracy if Location Accuracy is pinned to' ' reduced', () async { // Arrange final methodChannel = MethodChannelMock( @@ -185,7 +187,8 @@ void main() { ]); }); - test('Should receive reduced accuracy if Location Accuracy is already set' + test( + 'Should receive reduced accuracy if Location Accuracy is already set' ' to precise location accuracy', () async { // Arrange MethodChannelMock( @@ -797,7 +800,8 @@ void main() { }); }); - group('getPositionStream: When requesting a stream of position updates', () { + group('getPositionStream: When requesting a stream of position updates', + () { group('And requesting for position update multiple times', () { test('Should return the same stream', () { final plugin = GeolocatorAndroid(); @@ -820,10 +824,10 @@ void main() { expect(firstStream == secondStream, true); // Add multiple subscriptions - StreamSubscription? firstSubscription = firstStream - .listen((event) {}); - StreamSubscription? secondSubscription = secondStream - .listen((event) {}); + StreamSubscription? firstSubscription = + firstStream.listen((event) {}); + StreamSubscription? secondSubscription = + secondStream.listen((event) {}); // Cancel first subscription firstSubscription.cancel(); @@ -877,10 +881,9 @@ void main() { final completer = Completer(); completer.future.timeout( const Duration(milliseconds: 50), - onTimeout: - () => fail( - 'getPositionStream should trigger done and not timeout.', - ), + onTimeout: () => fail( + 'getPositionStream should trigger done and not timeout.', + ), ); final streamController = StreamController>.broadcast(); @@ -891,9 +894,9 @@ void main() { // Act GeolocatorAndroid().getPositionStream().listen( - (event) {}, - onDone: completer.complete, - ); + (event) {}, + onDone: completer.complete, + ); await streamController.close(); @@ -1468,7 +1471,8 @@ void main() { settings.foregroundNotificationConfig!.notificationIcon.name, ); expect( - jsonMap['foregroundNotificationConfig']['notificationIcon']['defType'], + jsonMap['foregroundNotificationConfig']['notificationIcon'] + ['defType'], settings.foregroundNotificationConfig!.notificationIcon.defType, ); expect( diff --git a/geolocator_android/test/method_channel_mock.dart b/geolocator_android/test/method_channel_mock.dart index 2419a86d9..549663e31 100644 --- a/geolocator_android/test/method_channel_mock.dart +++ b/geolocator_android/test/method_channel_mock.dart @@ -19,7 +19,7 @@ class MethodChannelMock { final log = []; MethodChannelMock({required String channelName, required this.methods}) - : methodChannel = MethodChannel(channelName) { + : methodChannel = MethodChannel(channelName) { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(methodChannel, _handler); } @@ -29,12 +29,10 @@ class MethodChannelMock { final method = methods.firstWhere( (MethodMock methodMock) => methodMock.methodName == methodCall.method, - orElse: - () => - throw MissingPluginException( - 'No implementation found for ' - 'method ${methodCall.method} on channel ${methodChannel.name}', - ), + orElse: () => throw MissingPluginException( + 'No implementation found for ' + 'method ${methodCall.method} on channel ${methodChannel.name}', + ), ); return Future.delayed(method.delay, () {