Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
3 of 5 tasks
jeffscaturro-aka opened this issue Sep 13, 2023 · 7 comments
Open
3 of 5 tasks
Assignees
Labels
P2 Important issues not at the top of the work list. platform: ios Issue is related to the iOS platform refactor Issues marked with refactor should be considered when refactoring the plugin. type: bug Something isn't working

Comments

@jeffscaturro-aka
Copy link

Please check the following before submitting a new issue.

Please select affected platform(s)

  • Android
  • iOS
  • Windows

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
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
Copy link
Author

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

@JeroenWeener JeroenWeener added platform: ios Issue is related to the iOS platform type: bug Something isn't working P2 Important issues not at the top of the work list. labels Sep 14, 2023
@niksen75
Copy link

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 mvanbeusekom self-assigned this Sep 20, 2023
@mvanbeusekom mvanbeusekom added the refactor Issues marked with refactor should be considered when refactoring the plugin. label Sep 20, 2023
@mvanbeusekom
Copy link
Member

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:

  • iOS will immediately call the locationManager:didChangeAuthorizationStatus: callback with the kCLAuthorizationStatusAuthorizedWhenInUse status (this happens before the second permission dialog is shown).
  • Next iOS will show the permission dialog asking the user to "Keep using While in Use" or "Change to Allow Always".
  • If the user selects "Keep using While in Use" iOS will close the dialog but will not inform us of any status updates.

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:

  • Before the second dialog is shown iOS will call the locationManager:didChangeAuthorizationStatus: callback and confirms the current permission reporting the kCLAuthorizationStatusAuthorizedWhenInUse status.
  • iOS will show the permission dialog asking the user to "Keep using While in Use" or "Change to Allow Always".
  • Only if the user select "Change to Allow Always", iOS will call the locationManager:didChangeAuthorizationStatus: method and inform us of the new kCLAuthorizationStatusAuthorizedAlways status.

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
Copy link
Author

@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
Copy link

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
Copy link

@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
Copy link

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P2 Important issues not at the top of the work list. platform: ios Issue is related to the iOS platform refactor Issues marked with refactor should be considered when refactoring the plugin. type: bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants