flutter-permission-handler icon indicating copy to clipboard operation
flutter-permission-handler copied to clipboard

Getting PermanentlyDenied when I request bluetooth permission

Open thormj opened this issue 2 years ago • 13 comments

Only seems to happen on iOS; When I do a Permission.bluetooth.request(), I'm getting back "PermantentlyDenied" with no dialog... I have this in my info.plist:

        <key>NSBluetoothAlwaysUsageDescription</key>
	<string>We need BT access because it's a BT App.</string>
	<key>NSBluetoothPeripheralUsageDescription</key>
	<string>We need BT access because it's a BT App.</string>
	<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
	<string>Location required for BLE devices</string>
	<key>NSLocationAlwaysUsageDescription</key>
	<string>Location required for BLE devices</string>
	<key>NSLocationWhenInUseUsageDescription</key>
	<string>Location required for BLE devices</string>

And I do the following in main.dart:

  void bleScan() async {
    print("bleScan");
    bluetoothok = false;

    //Ask for runtime permissions if necessary.
    var status = await Permission.bluetooth.request();
    if (status.isPermanentlyDenied) {
    setState(() {
      title = "foto-captor No Bluetooth";
    	print("BLEpermdisabled");
	    });
    	return;
    	}

    status = await Permission.bluetoothConnect.request();
    if (status.isPermanentlyDenied) {
    setState(() {
      title = "foto-captor Bluetooth NoConnect";
    	print("ConnectPermdisabled");
	    });
    	return;
    	}

    status = await Permission.bluetoothScan.request();
    if (status.isPermanentlyDenied) {
    	setState(() {
      		title = "foto-captor Can't Scan";
    	print("ScanPermdisabled");
	    });
    	return;
    	}

    myFlutterBlue.flutterBlue.setLogLevel(LogLevel.notice);

I've heard that Apple says you need to request permissions after the UI --- so is the request() only done once, and where should I put it in the code (I put it in the DoScan because that's what kicks everything off for me, but that fires before the UI appears)...

thormj avatar Apr 08 '22 22:04 thormj

Hi @thormj,

Did you enable the permission macros in your ios/Podfile as mentioned in the iOS section of the README.md?

This is required to ensure the code for requesting the permissions you need are compiled into the source code.

mvanbeusekom avatar Apr 09 '22 06:04 mvanbeusekom

Any update on this one? I'm experiencing the same issue

Yahllil avatar Apr 10 '22 08:04 Yahllil

Hi @Yahllil,

Did you have a look at my previous comment?

mvanbeusekom avatar Apr 10 '22 10:04 mvanbeusekom

@mvanbeusekom trying that... I think this is correct for BLE; I'll post an update when I get back to the iPhone:

# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
    # You can enable the permissions needed here. For example to enable camera
      # permission, just remove the `#` character in front so it looks like this:
      #
      # ## dart: PermissionGroup.camera
      # 'PERMISSION_CAMERA=1'
      #
      #  Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',

        ## dart: PermissionGroup.calendar
        # 'PERMISSION_EVENTS=1',

        ## dart: PermissionGroup.reminders
        # 'PERMISSION_REMINDERS=1',

        ## dart: PermissionGroup.contacts
        # 'PERMISSION_CONTACTS=1',

        ## dart: PermissionGroup.camera
        # 'PERMISSION_CAMERA=1',

        ## dart: PermissionGroup.microphone
        # 'PERMISSION_MICROPHONE=1',

        ## dart: PermissionGroup.speech
        # 'PERMISSION_SPEECH_RECOGNIZER=1',

        ## dart: PermissionGroup.photos
        # 'PERMISSION_PHOTOS=1',

        ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
        'PERMISSION_LOCATION=1',

        ## dart: PermissionGroup.notification
        # 'PERMISSION_NOTIFICATIONS=1',

        ## dart: PermissionGroup.mediaLibrary
        # 'PERMISSION_MEDIA_LIBRARY=1',

        ## dart: PermissionGroup.sensors
        # 'PERMISSION_SENSORS=1',   

        ## dart: PermissionGroup.bluetooth
        'PERMISSION_BLUETOOTH=1',

        ## dart: PermissionGroup.appTrackingTransparency
        # 'PERMISSION_APP_TRACKING_TRANSPARENCY=1',

        ## dart: PermissionGroup.criticalAlerts
        # 'PERMISSION_CRITICAL_ALERTS=1'
      ]
    end
  end
end

thormj avatar Apr 11 '22 16:04 thormj

It reliably asks for the "Use Bluetooth Connection," but it just quits with Permission.bluetoothConnect.request returning PermanentlyDenied...

My Code

    //Ask for runtime permissions if necessary.
    var status = await Permission.bluetooth.request();
    if (status.isPermanentlyDenied) {
    setState(() {
      title = "foto-captor No Bluetooth";
    	print("BLEpermdisabled");
	    });
    	return;
    	}

    status = await Permission.bluetoothConnect.request();
    if (status.isPermanentlyDenied) {
    setState(() {
      title = "foto-captor Bluetooth NoConnect";
    	print("ConnectPermdisabled");
	    });
    	return;
    	}

    status = await Permission.bluetoothScan.request();
    if (status.isPermanentlyDenied) {
    	setState(() {
      		title = "foto-captor Can't Scan";
    	print("ScanPermdisabled");
	    });
    	return;
    	}

    myFlutterBlue.flutterBlue.setLogLevel(LogLevel.notice);
    setState(() {
      title = "foto-captor " + indBLE;
    });

    bluetoothok = true;
    doingthis=false;
    print("GoTime... startscan.");
    myFlutterBlue.delScanStatusHandler();
    myFlutterBlue.addScanStatusHandler(scanStatusHandler);
    if (!myFlutterBlue.scanactive)
      await myFlutterBlue.startScan((sr) {
        scanResult(sr);
      }, allowDuplicates: true);

thormj avatar Apr 12 '22 18:04 thormj

At one point it said "you can ask for multiple permissions at the same time" -- how do you do that when it's Permission.XXX.request -- Permission.(Bluetooth | Connection | Location).Request?

thormj avatar Apr 12 '22 18:04 thormj

Oh, and can I do this as part of initstate? It seems odd to put it someplace else..

thormj avatar Apr 12 '22 18:04 thormj

Any update on this ?

Me also Failed to request Permission in IOS .

libindstme avatar Apr 13 '22 08:04 libindstme

@libindstme are you also requesting Bluetooth permissions?

mvanbeusekom avatar Apr 13 '22 08:04 mvanbeusekom

@thormj, thank you for supplying the example code and detailed reproduction steps, I will be looking into the behavior and get back to you a.s.a.p.

Regarding your other questions:

At one point it said "you can ask for multiple permissions at the same time" -- how do you do that when it's Permission.XXX.request -- Permission.(Bluetooth | Connection | Location).Request?

Requesting multiple permissions can be done using an array of permissions (p.s. note the the Connection permission in your example doesn't exists):

Map<Permission, PermissionStatus> statuses = await [
  Permission.bluetooth,
  Permission.location,
].request();

Oh, and can I do this as part of initstate? It seems odd to put it someplace else..

Yes the initState would be a good place to request / handle permissions. Most important part is to not request permissions as part of your build method as this could result in multiple calls.

mvanbeusekom avatar Apr 13 '22 08:04 mvanbeusekom

A-ha! I'm getting PermanentlyDenied on things that "aren't in iOS"-- Can we have things that aren't necessary in one platform or another return "granted" or "not used" or something? eg, on IOS: bluetoothConnect, bluetoothScan

If it's permanently denied, it makes sense to open the app settings for "yes, I need Bluetooth," but if there isn't a permission for that (I'm assuming it will just work)... presenting the user with an app settings dialogue doesn't make much sense.

I'm kinda assuming I do need those permissions... maybe I don't? -- on my Android, it appears I don't but I wrapped them in a Platform.isAndroid just to fix it for certain.

Should I make this post into a bug?

thormj avatar Apr 13 '22 16:04 thormj

have the same problem, permissions (bluetoothScan, bluetoothConnect) always PermanentlyDenied but bluetooth scan working.

karabanovbs avatar Jun 18 '22 19:06 karabanovbs

What could be the issue here is the line:

        CBManagerAuthorization blePermission = [_centralManager authorization];
        return [BluetoothPermissionStrategy parsePermission:blePermission];

at https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/strategies/BluetoothPermissionStrategy.m

I don't think this is the way how you listen for bluetooth permission result. Basically, when you construct CBCentralManager that will invoke the permission prompt. BUT, the user didn't yet click anything, and calling authorization right on the next line is not helpful as it will always return notDetermined.

The proper way would be to use a delegate and listen for a change:

    @objc func centralManagerDidUpdateState(_ central: CBCentralManager) {
       // This has to be called, as user cannot dismiss prompt. He will either clic:
       //  - allow -> _central.state == .poweredOn or .poweredOff
       //  - don't allow -> _central.state == .unathorized
    }

But that requires a bit of refactoring inside strategy as it should work async.

itsJoKr avatar Aug 29 '22 13:08 itsJoKr

i also experience this problem with ios and bluetooth permissions will there be an update or any other suggestions (e.g. workarounds).

    final Map<Permission, PermissionStatus> statuses = await <Permission>[
      if(Platform.isAndroid)Permission.bluetoothConnect,
      if(Platform.isAndroid)Permission.bluetoothScan,
      if(Platform.isIOS)Permission.bluetooth
      //Permission.location,
    ].request();

andrepura avatar Dec 14 '22 13:12 andrepura

Same issue here, on iOS 16.

{Permission.bluetooth: PermissionStatus.permanentlyDenied}

Bluetooth is enabled in the app settings.

hm122 avatar Feb 04 '23 10:02 hm122

Same issue here

electricmonk avatar Apr 20 '23 11:04 electricmonk

same issue here. permanently denied on iOS 13

  • macros to podfile was added
  • keys to Info.plist was added

any clue?

yahacom avatar Apr 26 '23 18:04 yahacom

Hi everyone!

I recently looked into a similar issue and believe @itsJoKr might be right. This would basically come down to a race condition in the Objective-C code.

I will investigate and (hopefully) come back with a PR that resolves this issue.

JeroenWeener avatar Jul 11 '23 12:07 JeroenWeener

Hi everyone!

We just landed some changes to the code relevant for this issue. As a by-product, this issue might be solved. Can anyone verify whether the issue still exists on the latest version of the plugin? Make sure that the latest version of permission_handler_apple (9.1.4) is being used by checking the pubspec.lock file. If it is not, you can pull it in by running flutter pub upgrade.

JeroenWeener avatar Jul 13 '23 09:07 JeroenWeener

Without additional information, we are unfortunately not able to resolve this issue. Therefore, we reluctantly closed this issue for now. If you run into this issue later, feel free to file a new issue with a reference to this issue. Add a description of detailed steps to reproduce, expected and current behaviour, logs and the output of 'flutter doctor -v'. Thanks for your contribution.

github-actions[bot] avatar Jul 27 '23 10:07 github-actions[bot]

Updating to the latest version in pubs.yml didnt solve.

Podfile and info.plist permissions are OK.

.lock: permission_handler: dependency: "direct main" description: name: permission_handler sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" url: "https://pub.dev" source: hosted version: "10.4.3" permission_handler_android: dependency: transitive description: name: permission_handler_android sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" url: "https://pub.dev" source: hosted version: "10.3.3" permission_handler_apple: dependency: transitive description: name: permission_handler_apple sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted version: "9.1.4"

cl0v avatar Aug 14 '23 17:08 cl0v

Hello everyone, ios and android are different solutions.

void requestBluetoothPermission() async {
    if (Platform.isAndroid) {
      [Permission.bluetoothScan, Permission.bluetoothConnect]
          .request()
          .then((status) {
        if (status[Permission.bluetoothScan] == PermissionStatus.granted &&
            status[Permission.bluetoothConnect] == PermissionStatus.granted) {
          bluetoothIsGranted.value = true;
        } else {
          bluetoothIsGranted.value = false;
        }
      });
    }
    if (Platform.isIOS) {
      [Permission.bluetooth].request().then((status) {
        if (status[Permission.bluetooth] == PermissionStatus.granted) {
          bluetoothIsGranted.value = true;
        } else {
          bluetoothIsGranted.value = false;
        }
      });
    }
  }

nasawz avatar Sep 11 '23 14:09 nasawz

Hi everyone,

I have been unable to reproduce this issue on a physical iPhone running iOS 16. I have used the code provided in the original post, combined with the code in this comment.

For me, the code works as expected on the latest version of the plugin (11.0.0). The call to Permission.bluetooth opens a dialog and returns once the user made a choice. The status that is returned corresponds to the choice the user made.

Note: the Permission.bluetoothScan and Permission.bluetoothConnect permissions are Android-only, and will always return PermissionStatus.permanentlyDenied on iOS devices.

If you are experiencing this problem, please provide me with the details of your setup, including:

  • iOS version
  • Plugin version
  • A minimal reproducible code sample in the form of a main.dart file
  • The contents of your Podfile and Info.plist

JeroenWeener avatar Sep 22 '23 13:09 JeroenWeener

Without additional information, we are unfortunately not able to resolve this issue. Therefore, we reluctantly closed this issue for now. If you run into this issue later, feel free to file a new issue with a reference to this issue. Add a description of detailed steps to reproduce, expected and current behaviour, logs and the output of 'flutter doctor -v'. Thanks for your contribution.

github-actions[bot] avatar Oct 06 '23 13:10 github-actions[bot]