Skip to content

Breaking changes in 7.0.0

Maurits van Beusekom edited this page Feb 15, 2021 · 5 revisions

Version 7.0.0 of the geolocator contains four important (and breaking) changes. This page will discuss these changes in detail.

Stable release for null safety.

For users that migrated to null-safety and there shouldn't be any big surprises. A few take aways:

  1. The getLastKnownPosition returns a Future<Position?>. This is because if the last known position isn't available Android and iOS will both return a null value;
  2. The timeLimit parameter of the getCurrentPosition method accepts a null value, meaning the method will not timeout while waiting for a position to be resolved;
  3. The intervalDuration and timeLimit parameters of the getPositionStream method accept a null value, which means respectively that the geolocator will not take any interval filter into account and will not timeout when waiting for a position to be resolved.

All other return values and parameters are guaranteed not to be null and should be treated as such.

Android permission update.

Before version 7.0.0 the geolocator used (or misused) the Android shouldShowRequestPermissionRationale to help determine if a user indicated not to ask permissions again. Pre API 30 the user could do so by checking the "Don't ask me again" checkbox, from API 30 and higher Android automatically infers this the second time a user denies permissions. The shouldShowRequestPermissionRationale method can be used to determine if, after the user initially denied permissions, the App should show an explanatory UI to the user explaining why the App needs location permissions (more details can be found here).

Unfortunately the shouldShowRequestPermissionRationale method returns false when the App request permission for the very first time but also once the user has indicated permissions should not be asked again. And only returns true once the user explicitly denied permissions when requesting permission but didn't indicate the App should not ask again (remember on API 30 Android infers this automatically the second time the user denied permissions).

So the logic that the geolocator applied was as follows:

  1. When the user denies permission the geolocator checks the result of the shouldShowRequestPermissionRationale and acts as follows:
    1. If true is returned than the geolocator will return LocationPermission.denied, meaning permission is denied but the App can ask again;
    2. If false is returned than the geolocator will return LocationPermission.deniedForever, meaning permission is denied and the App should not (and can not) ask again. If the App does request permissions another time it will immediately return LocationPermission.deniedForever;
  2. The above result is also stored as a variable in the shared preferences. This way when checking for permissions (using the checkPermission method) the geolocator is able to determine if the permission had been denied forever in earlier runs of the application:
    1. If the last time the App requested permission the permission was denied forever but the shouldShowRequestPermissionRationale returns true, the geolocator returns LocationPermission.denied and the App is allowed to request permissions again;
    2. If the last time the App requested permission the permission was denied forever and the shouldShowRequestPermissionRationale returns false, the geolocator returns LocationPermission.deniedForever and the developer knows the App cannot request permissions and should redirect the user to the settings;

Unfortunately with the introduction of the "Ask every time" permission in Android API 30, storing the permission results in a bug. To describe the situation in which the bug occurs assume that the App requested permissions twice and both times they were denied. The geolocator now stores that permissions are denied forever in the shared preferences. Next the user reconsiders and decides that for once the App can have access to the location services of the device and changes permissions in the App settings to "Ask every time". Android will reset all earlier permissions and assumes permissions have never been requested before. So when the App starts and permissions are checked, Android will return that permissions are denied as Android always denies permissions implicitly until the user explicitly grants permissions. This is where the bug shows up, since the geolocator detects the value stored earlier in the shared preferences and determines that since Android reports permissions are denied and the value in the shared preferences indicates that last time permissions were denied forever, permissions should still be denied forever (even though the user indicated in the Android settings that permissions can be requested again).

After careful consideration we decided that the geolocator should not be storing the permission result in the shared preferences anymore. This means that the checkPermission method will no longer be able to detect if permissions are denied forever. From version 7.0.0 and onwards the checkPermission will return one of the following permissions (note that this effects all API levels):

  1. LocationPermission.denied: indicates that permissions to access the location services have been denied;
  2. LocationPermission.whenInUse: indicates that permissions are granted while the App is in use (this result is returned when the user selects "Ask every time" or "Only when in use");
  3. LocationPermission.always: indicates that permissions are granted even if the App is running in the background (note that the geolocator doesn't support running in the background but the permissions do).

When requesting permissions we can still detect if permissions have been simply denied or if they have been denied forever. This means that the requestPermission method will still report if permissions are denied forever. There fore we suggest users of the geolocator plugin to use the following flow:

bool serviceEnabled;
LocationPermission permission;

// Test if location services are enabled.
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
  // Location services are not enabled don't continue
  // accessing the position and request users of the 
  // App to enable the location services.
  return;
}

permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
  permission = await Geolocator.requestPermission();
  if (permission == LocationPermission.deniedForever) {
    // Permissions are denied forever, handle appropriately. 
    return;
  } 

  if (permission == LocationPermission.denied) {
    // Permissions are denied, next time you could try
    // requesting permissions again (this is also where
    // Android's shouldShowRequestPermissionRationale 
    // returned true. According to Android guidelines
    // your App should show an explanatory UI now.
    return;
  }
}

// When we reach here, permissions are granted and we can
// continue accessing the position of the device.
_position = await Geolocator.getCurrentPosition();

Removed deprecated timeInterval parameter from the getPositionStream method.

Since version 6.1.0 the int timeInterval parameter of the Geolocator.getPositionStream method has been deprecated on favour of the Duration intervalDuration parameter. In version 7.0.0 the timeInterval parameter will be completely removed.

Removed deprecated global methods.

With release 7.0.0 we removed the deprecated global methods (deprecated in 6.1.0) from the code base. A complete overview of the method to be removed and its replacement method is listed below.

Removed method Replacement method
checkPermission() Geolocator.checkPermission()
requestPermission() Geolocator.requestPermission()
isLocationServiceEnabled() Geolocator.isLocationServiceEnabled()
getLastKnownPosition({ bool forceAndroidLocationManager = false }) Geolocator.getLastKnownPosition({ bool forceAndroidLocationManager = false })
getCurrentPosition({ LocationAccuracy desiredAccuracy = LocationAccuracy.best, bool forceAndroidLocationManager = false, Duration timeLimit }) Geolocator.getCurrentPosition({ LocationAccuracy desiredAccuracy = LocationAccuracy.best, bool forceAndroidLocationManager = false, Duration? timeLimit })
getPositionStream({ LocationAccuracy desiredAccuracy = LocationAccuracy.best, int distanceFilter = 0, bool forceAndroidLocationManager = false, int timeInterval = 0, Duration timeLimit }) Geolocator.getPositionStream({ LocationAccuracy desiredAccuracy = LocationAccuracy.best, int distanceFilter = 0, bool forceAndroidLocationManager = false, Duration? intervalDuration, Duration? timeLimit })
openAppSettings() Geolocator.openAppSettings()
openLocationSettings() Geolocator.openLocationSettings()
distanceBetween(double startLatitude, double startLongitude, double endLatitude, double endLongitude) Geolocator.distanceBetween(double startLatitude, double startLongitude, double endLatitude, double endLongitude)
bearingBetween(double startLatitude, double startLongitude, double endLatitude, double endLongitude) Geolocator.bearingBetween(double startLatitude, double startLongitude, double endLatitude, double endLongitude)