Baseflow / flutter-permission-handler

Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
https://baseflow.com
MIT License
2.04k stars 850 forks source link

[Bug]: Requesting locationAlways does not await user's response. #1152

Open jeffscaturro-aka opened 1 year ago

jeffscaturro-aka commented 1 year ago

Please check the following before submitting a new issue.

Please select affected platform(s)

Steps to reproduce

  1. Check result of requesting locationWhenInUse permissions await Permission.locationWhenInUse.request().
  2. Check result of requesting locationAlways permissions await Permission.locationAlways.request().

Expected results

We expect the both requests to properly await the user's response to the iOS system dialog, and returns the user's choice.

Actual results

In the above steps, the first request properly awaits the user's response to the iOS system dialog, and returns the user's choice.

The second request immediately returns a response of not granted because the user hasn't responded yet to the dialog.

Code sample

  Future<bool> _requestLocationWhileInUse() async {
    // This actually waits for user to respond to dialog prompt before
    // continuing to return the status.isGranted.
    final status = await Permission.locationWhenInUse.request();

    return status.isGranted;
  }

  Future<bool> _requestLocationAlways() async {
    // This does not wait for user to respond to dialog prompt before
    // continuing to return the status.isGranted, thus, we often receive
    // false here, and have to check after the user responds to the dialog.
    final status = await Permission.locationAlways.request();

    return status.isGranted;
  }

Screenshots or video

Screenshots or video demonstration [Upload media here]

Version

10.4.3 - 11.0.0

Flutter Doctor output

Doctor output ```console Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.13.0, on macOS 13.5.1 22G90 darwin-arm64, locale en-US) [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2) [✓] Xcode - develop for iOS and macOS (Xcode 14.3.1) [✓] Chrome - develop for the web [✓] Android Studio (version 2022.3) [✓] VS Code (version 1.81.1) [✓] Connected device (5 available) [✓] Network resources • No issues found! ```
jeffscaturro-aka commented 1 year ago

Looks like a similar issue was previously filed around this, but marked as resolved: https://github.com/Baseflow/flutter-permission-handler/issues/1086 (=> https://github.com/Baseflow/flutter-permission-handler/pull/1089/files#r1254302197).

niksen75 commented 1 year ago

Same issue.

iOS 16.6.1, physical device

Pubspec.yaml permission_handler: ^10.4.5 permission_handler_apple: ^9.1.4

Podfile config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)',

dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]

    'PERMISSION_LOCATION=1',

]

mvanbeusekom commented 1 year ago

Hi @jeffscaturro-aka, @niksen75,

Thank you for reporting this issue and providing detailed descriptions. I was able to reproduce the problem and did more detailed investigation.

Unfortunately the issue is not really easy to solve in our current architecture where we simply await the future when requesting permissions. The reason is that iOS will not always provide a status update when requesting "Always allow" permissions. Here are the situations:

User selected "Allow once"

When requesting location permissions and the user initially selected "Only once", requesting locationAlways will not trigger the permission dialog at all and no result is provided. Unfortunately Apple doesn't provide an API which informs us the user initially selected "Only once", instead the "When in use" permission is returned. This is also explained in Apple's documentation.

User selected "When in use" and follows up with "Keep When in use"

When the user selected "When in use" at the moment we initially request location permissions Apple will return the "When in use" permission. Now if the app requests location always permission the following happens:

User selected "When in use" and follows up with "Change to Allow always"

Again the user initially provided "When in use" permissions and the app is requesting location always permission:

As you can see this makes it really hard (not to say impossible) to reliably await for the correct status when requesting location alway permission. This due to the fact that in several situations iOS will simply not inform us about the status when the user makes a selection.

We understand that the current solution provided by the plugin is not really helpful and therefore we are planning to do a refactor of the plugin where we will provide developers with the option to listen to a stream providing permission status changes instead of enforcing developers to await the request method for a status update. We will start the refactor very soon and hopefully can provide updates in the coming months. Until this time, we are not really able to provide a more solid solution and we hope you all understand.

We will keep this issue open to help track the refactor process and ensure this issue will be resolved as part of the refactor process.

P.S. alternative ideas are of course welcome.

jeffscaturro-aka commented 1 year ago

@mvanbeusekom thank you so much for the triage and in depth explanation here, it is much appreciated and gives a great understanding of the limitations we're facing. I feel most developers can understand Apple doesn't always make it easy on us, especially when factoring in user privacy and permissions (rightfully so).

A workaround we had in place was simply utilizing a WidgetsBindingObserver where we were asking for this permission and listening to its lifecycle state changes (didChangeAppLifecycleState). I hope that helps some others who stumble across this issue, and of course, other ways around this would be welcomed to include on this thread!

Biowulf21 commented 8 months ago

For anyone reading this in the future, this is the didChangeAppLifecycleState workaround that people are mentioning:

class _LocationPageState extends State<LocationPage>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  // this is used to listen to the app lifecycle state
  // this function is called affter the OS location permission popup appears because
  // the app is considered "paused" when that happens and "resumed" after the user
  // has selected an option
  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed && Platform.isIOS) {
      print('resumed ios');
      await handleIOSLocationWorkAround(context, bloc);
    }
  }
nayanAubie commented 7 months ago

@Biowulf21 What is the exact workaround and what will your method handleIOSLocationWorkAround do? I am just wondering as there is no connection between AppLifecycleState and this issue.

Biowulf21 commented 7 months ago

@Biowulf21 What is the exact workaround and what will your method handleIOSLocationWorkAround do? I am just wondering as there is no connection between AppLifecycleState and this issue.

The app goes to suspend state when the location permission pop up appears and goes to resumed state when it's closed.