flutter_blue
flutter_blue copied to clipboard
Support new Android 12 Bluetooth permissions
Android 12 introduces new Bluetooth permissions:
BLUETOOTH_SCAN & BLUETOOTH_CONNECT
https://developer.android.com/about/versions/12/features/bluetooth-permissions
The plugin should update its manifest.xml to match the new format outlined in the documentation.
The documentation in the README should reflect this change.
It should also update its permission handling. On Android 11 and lower: Use the current permission request logic. On Android 12+: Use permission logic that matches the required behavior:
When calling startScan() we should request permission for BLUETOOTH_SCAN, prior to actually starting the scan.
When calling device.connect() we should request permission for BLUETOOTH_CONNECT.
Strong agree!
My own flutter_blue fork that supports Android 12 permission: https://github.com/espresso3389/flutter_blue
It's still under development but the example code seems work correctly. I'm preparing PR but the changes are relatively large and I'm not sure it can be safely merged or not...
Could you fork off of your repo from this https://github.com/boskokg/flutter_blue/ repo or make a PR against it? That repo has many more PRs https://github.com/pauldemarco/flutter_blue/issues/929#issuecomment-930759129
Android 12 support is badly needed.
Sorry, look like the fork I mentioned already has 12 support.
How do I use the espresso3389 fork in my Flutter project instead of the pauldemarco one?
@tricaricom Do like the following on your pubspec.yaml:
flutter_blue:
git: https://github.com/espresso3389/flutter_blue
Thanks @espresso3389 .
So when I use your flutter_blue, I get this error:
E/AndroidRuntime(15548): java.lang.SecurityException: Need android.permission.BLUETOOTH_CONNECT permission for android.content.AttributionSource@41052d27: AdapterService getRemoteName
However, I have these permissions in the manifest:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
This is interesting because I explicitly added BLUETOOTH_CONNECT here hoping to overcome this error. But when I look in your README, you do not include this permission. Very confusing....
If it makes any difference, I am testing on a Google Pixel 6 running Android 12.
@tricaricom Same issue is reported here but I could not reproduce the issue with flutter_blue sample and my own codes...
Can you provide me with the complete sample that causes the issue?
@espresso3389 what repo/branch should I use for testing?
@ekuleshov I'm working on "my" master branch, so the following lines are enough:
flutter_blue:
git: https://github.com/espresso3389/flutter_blue
@espresso3389 not sure if it is related, for my app I have compileSdkVersion 31 and targetSdkVersion 30 and I'm running on Pixel 3xl. For some reason I also can't reproduce it with the example app, but it is failing consistently with my own app.
When running on Android 12, the system settings show only one permission "nearby devices" even so on the API side there are two separate permissions SCAN and CONNECT. It seems like manually granting nearby permission in the system settings grants both SCAN and CONNECT ones on my device.
All in all it seems like both SCAN and CONNECT permissions need to be requested and granted when calling BLE scan. E.g. see https://xizzhu.me/post/2021-10-05-android-12-bluetooth-permissions/
The CONNECT error is thrown from the ProtoMaker.from(result.getDevice(), result) from inside getScanCallback21():
static Protos.BluetoothDevice from(BluetoothDevice device) {
Protos.BluetoothDevice.Builder p = Protos.BluetoothDevice.newBuilder();
p.setRemoteId(device.getAddress());
String name = device.getName(); <--- THIS ONE REQUIRES connect permission
...
Note that BluetoothDevice.getName() is declared like this:
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public String getName() {
...
Also, perhaps to avoid the app crash the code ingetScanCallback21() method need to be wrapped with a try/catch to avoid fatal crashes for an entire app.
@ekuleshov The plugin (on the fork) basically requires both compileSdkVersion and targetSdkVersion set to 31:
https://github.com/espresso3389/flutter_blue/blob/80fca568e59428722490a8cae3c56a039c4dc283/android/build.gradle#L31
And, the example also does the same.
I'm not sure what happens if you set targetSdkVersion 30 on your code...
I'm not sure what happens if you set
targetSdkVersion 30on your code...
I already tried to bump the targetSdkVersion to 31 in my app, but still getting the same error for CONNECT permission.
If I go to the system settings, remove nearby permission and then grant it again the scan in my app is working after that. So, I still think both permissions need to be requested for the scan. Note the annotation on the BluetoothDevice.getName() which is called by scan.
@ekuleshov
I still think both permissions need to be requested for the scan. Note the annotation on the BluetoothDevice.getName() which is called by scan.
Ah, I see your point. I'll update the permission logic to deal with the permissions.
And, the example also does the same.
BTW, when you do a fresh install for the example app and decline Bluetooth permission requested on the first BLE scan attempt the app gets a null pointer dereference error when getting the snapshot.data! value. It seems like it need to check and handle the snapshot.error before unconditionally retrieving snapshot.data!.
StreamBuilder<List<BluetoothDevice>>(
stream: Stream.periodic(Duration(seconds: 2))
.asyncMap((_) => FlutterBlue.instance.connectedDevices),
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data! <-- HERE
.map((d) => ListTile( ...
It'd be so great if https://github.com/espresso3389/flutter_blue/ would contribute to https://github.com/boskokg/flutter_blue/ to unify efforts. boskokg also has null safety and permissions but a whole bunch more PRs.
@MrCsabaToth Yes, it's good if he can do so easily. There seems at least two problems:
- He seems so busy
- SDK 31 breaks so many things; some of them need changes on other Flutter plugins
Anyway, PR #940 is for the issue.
@ekuleshov Try the latest commit (f0d9cb), I've changed the way to request/check permissions on scan. (I actually could not reproduce the issue but I think it handles the situations better).
@ekuleshov Try the latest commit (f0d9cb), I've changed the way to request/check permissions on scan. (I actually could not reproduce the issue but I think it handles the situations better).
Somehow it got worse. No permission request on a fresh install and I get this error now when scan() is called:
E/flutter ( 5232): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: PlatformException(startScan, Need android.permission.BLUETOOTH_SCAN permission for android.content.AttributionSource@7c328bd: GattService registerScanner, java.lang.SecurityException: Need android.permission.BLUETOOTH_SCAN permission for android.content.AttributionSource@7c328bd: GattService registerScanner
E/flutter ( 5232): at android.os.Parcel.createExceptionOrNull(Parcel.java:2425)
E/flutter ( 5232): at android.os.Parcel.createException(Parcel.java:2409)
E/flutter ( 5232): at android.os.Parcel.readException(Parcel.java:2392)
E/flutter ( 5232): at android.os.Parcel.readException(Parcel.java:2334)
E/flutter ( 5232): at android.bluetooth.IBluetoothGatt$Stub$Proxy.registerScanner(IBluetoothGatt.java:1727)
E/flutter ( 5232): at android.bluetooth.le.BluetoothLeScanner$BleScanCallbackWrapper.startRegistration(BluetoothLeScanner.java:426)
E/flutter ( 5232): at android.bluetooth.le.BluetoothLeScanner.startScan(BluetoothLeScanner.java:278)
E/flutter ( 5232): at android.bluetooth.le.BluetoothLeScanner.startScan(BluetoothLeScanner.java:154)
E/flutter ( 5232): at com.pauldemarco.flutter_blue.FlutterBluePlugin.startScan21(FlutterBluePlugin.java:846)
E/flutter ( 5232): at com.pauldemarco.flutter_blue.FlutterBluePlugin.startScan(FlutterBluePlugin.java:782)
E/flutter ( 5232): at com.pauldemarco.flutter_blue.FlutterBluePlugin.lambda$onMethodCall$0$FlutterBluePlugin(FlutterBluePlugin.java:246)
E/flutter ( 5232): at com.pauldemarco.flutter_blue.-$$Lambda$FlutterBluePlugin$npO5ZnMb5Xa1WrWJ1kqQqzqyO7E.op(Unknown Source:6)
E/flutter ( 5232): at com.pauldemarco.flutter_blue.FlutterBluePlugin.ensurePermissionsBeforeAction(FlutterBluePlugin.java:666)
E/flutter ( 5232): at com.pauldemarco.flutter_blue.FlutterBluePlugin.onMethodCall(FlutterBluePlugin.java:244)
E/flutter ( 5232): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
E/flutter ( 5232): at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:178)
E/flutter ( 5232): at io.flutter.embedding.engine.dart.DartMessenger.lambda$handleMessageFromDart$0$DartMessenger(DartMessenger.java:206)
E/flutter ( 5232): at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$6ZD1MYkhaLxyPjtoFDxe45u43DI.run(Unknown Source:12)
@ekuleshov Sorry, the condition is completely reversed... Please try 256f1a.
The @espresso3389 fork does not work properly on BOTH pre-Android 12 and post-Android 12 devices. I can get this to work fine on my Android 12 device. But when I test it on Android 9, it does discover the bluetooth device, but when I try to connect, I get this error:
E/MethodChannel#plugins.pauldemarco.com/flutter_blue/methods(18144): Failed to handle method call E/MethodChannel#plugins.pauldemarco.com/flutter_blue/methods(18144): java.lang.NullPointerException: Attempt to get length of null array
E/flutter (18144): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: PlatformException(error, Attempt to get length of null array, null, java.lang.NullPointerException: Attempt to get length of null array
E/flutter (18144): at androidx.core.app.ActivityCompat.requestPermissions(ActivityCompat.java:504)
E/flutter (18144): at com.pauldemarco.flutter_blue.FlutterBluePlugin.ensurePermissionsBeforeAction(FlutterBluePlugin.java:660)
E/flutter (18144): at com.pauldemarco.flutter_blue.FlutterBluePlugin.ensurePermissionBeforeAction(FlutterBluePlugin.java:650)
E/flutter (18144): at com.pauldemarco.flutter_blue.FlutterBluePlugin.onMethodCall(FlutterBluePlugin.java:282)
E/flutter (18144): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
E/flutter (18144): at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:178)
E/flutter (18144): at io.flutter.embedding.engine.dart.DartMessenger.lambda$handleMessageFromDart$0$DartMessenger(DartMessenger.java:206)
E/flutter (18144): at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$6ZD1MYkhaLxyPjtoFDxe45u43DI.run(Unknown Source:12)
E/flutter (18144): at android.os.Handler.handleCallback(Handler.java:873)
E/flutter (18144): at android.os.Handler.dispatchMessage(Handler.java:99)
E/flutter (18144): at android.os.Looper.loop(Looper.java:215)
E/flutter (18144): at android.app.ActivityThread.main(ActivityThread.java:6939)
E/flutter (18144): at java.lang.reflect.Method.invoke(Native Method)
E/flutter (18144): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
E/flutter (18144): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:870)
E/flutter (18144): )
E/flutter (18144): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:607:7)
E/flutter (18144): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:167:18)
E/flutter (18144):
It seems like it needs some better null handling. It passes null to allPermissionsGranted() which returns false and then it calls ActivityCompat.requestPermissions() on the same array. Either it should not use null at the line 654 (return an empty String[] instead) or either return true from the allPermissionsGranted() when argument is null.
@ekuleshov Now allPermissionsGranted returns null for ~~permissions==null~~ permissions==false case.
@ekuleshov Now
allPermissionsGrantedreturnsnullforpermissions==nullcase.
That works, thank you! Though from the error-proneness point of view it would be good to avoid using nulls values for anything. But that is also somewhat matter of a code style.
@ekuleshov
it would be good to avoid using nulls values for anything.
Yes, I don't want to use Java, null,... It's not originally my code :(
@MrCsabaToth Yes, it's good if he can do so easily. There seems at least two problems:
1. He seems so busy 2. SDK 31 breaks so many things; some of them need changes on other Flutter pluginsAnyway, PR #940 is for the issue.
What I thought is it'd be so awesome if your fork was forked from boskokg (https://github.com/boskokg/flutter_blue/ - which is a fork of pauldemarco) instead of directly from pauldemarco, because boskokg has a lot of PR merged.
@MrCsabaToth I've created a PR (boskokg/flutter_blue#29) on boskokg's fork.
@espresso3389 thank you. tomorrow I will check and merge it and increase the version.