Baseflow / flutter-permission-plugins

This repo contains a collection of permission related Flutter plugins which can be used to request permissions to access device resources in a cross-platform way.
https://baseflow.com
MIT License
52 stars 33 forks source link

CheckPermissionStatus returns denied after granted was returned when requesting permissions #29

Closed mvanbeusekom closed 4 years ago

mvanbeusekom commented 4 years ago
PermissionStatus geolocationPermissionStatus = await _locationPermissions.requestPermissions(permissionLevel: LocationPermissionLevel.locationWhenInUse);

returns PermissionStatus.granted

but after that

final PermissionStatus geolocationPermissionStatus = await _locationPermissions.checkPermissionStatus(level: LocationPermissionLevel.locationWhenInUse);

returns PermissionStatus.denied

On my manifest I have only this

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Originally posted by @DenisBogatirov in https://github.com/Baseflow/flutter-permission-plugins/issues/21#issuecomment-582366361

mvanbeusekom commented 4 years ago

Hi @DenisBogatirov, I have tried to reproduce this issue but I am failing. Could you provide some extra information maybe? Things that would help would be:

  1. Which version of the plugin are you using?
  2. Which Android version are you using and are you running on emulator or real device?

Here is the code I used to try and reproduce the error (I am on Android emulator with API29, using version 2.0.4+1 of the plugin) :

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.baseflow.location_permissions_example">

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="location_permissions_example"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- This keeps the window background of the activity showing
                 until Flutter renders its first frame. It can be removed if
                 there is no splash screen (such as the default splash screen
                 defined in @style/LaunchTheme). -->
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

main.dart:

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:location_permissions/location_permissions.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
          actions: <Widget>[
            IconButton(
              icon: const Icon(Icons.settings),
              onPressed: () {
                LocationPermissions().openAppSettings().then((bool hasOpened) =>
                    debugPrint('App Settings opened: ' + hasOpened.toString()));
              },
            )
          ],
        ),
        body: Center(
          child: ListView(
            children: _createWidgetList(),
          ),
        ),
      ),
    );
  }

  List<Widget> _createWidgetList() {
    List<Widget> widgets = [];

    if (Platform.isAndroid) {
      widgets.add(_PermissionWidget(LocationPermissionLevel.locationWhenInUse));
      widgets.add(_StreamingStatusWidget());
    } else if (Platform.isIOS) {
      widgets.add(_PermissionWidget(LocationPermissionLevel.locationWhenInUse));
      widgets.add(_PermissionWidget(LocationPermissionLevel.locationAlways));
    }

    return widgets;
  }
}

class _StreamingStatusWidget extends StatelessWidget {
  final Stream<ServiceStatus> statusStream =
      LocationPermissions().serviceStatus;

  @override
  Widget build(BuildContext context) => ListTile(
        title: const Text('ServiceStatus'),
        subtitle: StreamBuilder<ServiceStatus>(
          stream: statusStream,
          initialData: ServiceStatus.unknown,
          builder: (_, AsyncSnapshot<ServiceStatus> snapshot) =>
              Text('${snapshot.data}'),
        ),
      );
}

class _PermissionWidget extends StatefulWidget {
  const _PermissionWidget(this._permissionLevel);

  final LocationPermissionLevel _permissionLevel;

  @override
  _PermissionState createState() => _PermissionState(_permissionLevel);
}

class _PermissionState extends State<_PermissionWidget> {
  _PermissionState(this._permissionLevel);

  final LocationPermissionLevel _permissionLevel;
  PermissionStatus _permissionStatus = PermissionStatus.unknown;

  @override
  void initState() {
    super.initState();

    //_listenForPermissionStatus();
  }

  void _listenForPermissionStatus() {
    final Future<PermissionStatus> statusFuture =
        LocationPermissions().checkPermissionStatus();

    statusFuture.then((PermissionStatus status) {
      setState(() {
        _permissionStatus = status;
      });
    });
  }

  Color getPermissionColor() {
    switch (_permissionStatus) {
      case PermissionStatus.denied:
        return Colors.red;
      case PermissionStatus.granted:
        return Colors.green;
      default:
        return Colors.grey;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Padding(
        padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0),
        child: GestureDetector(
          onTap: () {
            requestPermission(_permissionLevel);
          },
          child: Row(
            children: <Widget>[
              Expanded(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: <Widget>[
                      Text(_permissionLevel.toString()),
                      Text(
                        _permissionStatus.toString(),
                        style: TextStyle(color: getPermissionColor()),
                      ),
                    ]),
              ),
              IconButton(
                  icon: const Icon(Icons.check),
                  onPressed: () {
                    checkPermission(context, _permissionLevel);
                  }),
              IconButton(
                  icon: const Icon(Icons.info),
                  onPressed: () {
                    checkServiceStatus(context, _permissionLevel);
                  }),
            ],
          ),
        ),
      ),
    );
  }

  void checkServiceStatus(
      BuildContext context, LocationPermissionLevel permissionLevel) {
    LocationPermissions()
        .checkServiceStatus()
        .then((ServiceStatus serviceStatus) {
      final SnackBar snackBar =
          SnackBar(content: Text(serviceStatus.toString()));

      Scaffold.of(context).showSnackBar(snackBar);
    });
  }

  Future<void> checkPermission(
    BuildContext context,
    LocationPermissionLevel permissionLevel,
  ) async {
    final PermissionStatus permissionResult =
        await LocationPermissions().checkPermissionStatus(
      level: permissionLevel,
    );

    final snackBar = SnackBar(
      content: Text("Permission status: $permissionResult"),
    );
    Scaffold.of(context).showSnackBar(snackBar);

    setState(() {
      _permissionStatus = permissionResult;
    });
  }

  Future<void> requestPermission(
      LocationPermissionLevel permissionLevel) async {
    final PermissionStatus permissionRequestResult = await LocationPermissions()
        .requestPermissions(permissionLevel: permissionLevel);

    setState(() {
      _permissionStatus = permissionRequestResult;
    });
  }
}
kjmj commented 4 years ago

Also having this issue. When the user selects LocationPermissionLevel.locationWhenInUse on android, the permission status is PermissionStatus.denied. Cant reproduce the same on iOS

We are using location_permissions 2.0.4+1 on an android emulator running android 10

fabiomgoncalves commented 4 years ago

Can confirm the bug, the following code is showing the same issue:

    PermissionStatus statusAsk = await LocationPermissions().requestPermissions(
        permissionLevel: LocationPermissionLevel.locationWhenInUse); // granted

    PermissionStatus statusCheck = await LocationPermissions().checkPermissionStatus(
        level: LocationPermissionLevel.locationWhenInUse); // denied

Tested in Android 10, both using a device (Samsung S10), and emulator using Android 10 as well. Importing version 2.0.4+1.

Also, using permission_handler instead correctly returns the expected value.

joknjokn commented 4 years ago

Having the same issue. Any solution?

khainke commented 4 years ago

Same issue here, Android 10 with location enabled while app is in use.

nshafer commented 4 years ago

Same issue exactly as @fabiomgoncalves describes it. requestPermissions(permissionLevel: LocationPermissionLevel.locationWhenInUse) returns granted but a followup checkPermissionStatus(level: LocationPermissionLevel.locationWhenInUse) returns denied if you click to only allow "when in-use". It only works if you allow permission "all the time". Thanks!

mvanbeusekom commented 4 years ago

Thanks for all the feedback, I struggled a bit reproducing the issue but I managed to reproduce it now as well (not sure why). I will now look into the bug and try to provide a fix a.s.a.p.

mvanbeusekom commented 4 years ago

Finally found the issue, the problem is related to the targetSdkVersion that is referenced. The example App that is part of the location_permissions plugin targets version 29.

However if you create a new application with flutter create by default the app references version 28. You can easily fix this by updating the compileSdkVersion and targetSdkVersion parameters in your android/app/build.gradle file and set them to 29.

I will have a look and see if I can create some kind of fix to fall back on the old permission handling when the configured SDK is set to 28. So if you want it fixed now please update to SDK version 29.

mvanbeusekom commented 4 years ago

I have had a look at the code and it seems that the implementation of the ContextCompat.checkSelfPermission() method has been changed in API version 29. From my tests I get the following:

So the only solution I can think of is to upgrade your android/app/build.gradle file and target SDK version 29.

nshafer commented 4 years ago

I can confirm that updating my compileSdkVersion and targetSdkVersion to 29 fixes the issue on Android 10, but it still behaves properly on older android versions (just asking for overall permission). Thank you very much for the fix!

brianb-sf commented 4 years ago

@mvanbeusekom I am still seeing this issue after changing the compileSdkVersion and the targetSdkVersion to API level 29. I have pinpointed the issue to the following:

If the application contains the <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> permission then the user is presented with a modal that has three options regardless of the enum value passed to requestPermissions(...):

If the user selects the Allow while in use option then subsequent requests to checkPermissionStatus(level: LocationPermissionLevel.whileInUse) will return PermissionsStatus.denied

Here is a gif of the example application above with what I am seeing when I add the <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> and then request location permissions using requestPermissions(level: LocationPermissionLevel.locationWhenInUse)

location-perms

Hope this helps, there is for sure some nuance in here that is hard to pick up on.

jensencelestial commented 4 years ago

Can also confirm what @brianb-sf is experiencing, with the same Manifest permissions even though targetSdkVersion is set to 29. checkPermissionStatus is returning denied when Allow location while the app is in use is selected.