flutter-permission-plugins
flutter-permission-plugins copied to clipboard
[android] "Can request only one set of permissions at a time."
🐛 Bug Report
Calling await LocationPermissions().requestPermissions();
with both "Fine" and "Coarse" permission in AndroidManifest.xml
gives the Can request only one set of permissions at a time.
in the logs. Also, giving PermissionStatus.denied
(even if approved) when checkPermissions
called after awaiting requestPermissions
.
Expected behaviour
The error/warning should not come in and correct status should be returned by checkPermissions
after requestPermissions
is called.
Reproduction steps
Code to reproduce:
Future<bool> _checkLocationPermission() async {
final checkPermission = await LocationPermissions().checkPermissionStatus();
printIfDebug("checkPermission: $checkPermission");
if (checkPermission == PermissionStatus.granted ||
checkPermission == PermissionStatus.restricted) return true;
return false;
}
Future<bool> _checkAndRequestLocationPermission() async {
// return true, if already have permission
if (await _checkLocationPermission()) return true;
// request permission
final _ = await LocationPermissions().requestPermissions();
printIfDebug("requestPermission: $_");
// check if permission was given
final hasPermission = await _checkLocationPermission();
// if no permission and "showShowPermissionRationale" then go to settings and return false
if (!hasPermission &&
await LocationPermissions().shouldShowRequestPermissionRationale()) {
// if shouldRequest false, then open app settings and return false
// TODO UI that shows why this permission is required
await LocationPermissions().openAppSettings();
return false;
}
return hasPermission;
}
This is how it it is used for checking access for location before calling WifiManager.startScan
api on android
Future<List<WifiNetwork>> scanWifi() async {
if (await _checkAndRequestLocationPermission())
return await WiFiForIoTPlugin.loadWifiList();
return null;
}
Configuration
Android SDK: 28
Version: ^3.0.0
Platform:
- [ ] :iphone: iOS
- [x] :robot: Android
I am calling checkPermissions
again after requestPermissions
instead of "checking" the returned value of requestPermissions
because it is returning Unkown
status (I assume it is because the Activity
is pausing-and-resuming after the permission is granted, I am using embedding v2).
Full logs:
I/flutter ( 4962): AppLifecycleState.inactive
I/flutter ( 4962): AppLifecycleState.inactive
I/flutter ( 4962): checkPermission: PermissionStatus.denied
...
W/Activity( 4962): Can request only one set of permissions at a time
...
I/flutter ( 4962): requestPermission: PermissionStatus.unknown
I/flutter ( 4962): checkPermission: PermissionStatus.denied
...
...
I/AppStateListener( 4962): onActivityStopped for activity : MainActivity isForegound : false
I/flutter ( 4962): AppLifecycleState.paused
I/flutter ( 4962): AppLifecycleState.paused
...
I/AppStateListener( 4962): onActivityStarted for activity : MainActivity isForegound : true
...
I/flutter ( 4962): AppLifecycleState.resumed
I/flutter ( 4962): AppLifecycleState.resumed
Also my guess the bug is in returning the result for requestPermissions
.
The following code is working now.
uture<bool> _checkLocationPermission() async {
final checkPermission = await LocationPermissions().checkPermissionStatus();
printIfDebug("checkPermission: $checkPermission");
if (checkPermission == PermissionStatus.granted ||
checkPermission == PermissionStatus.restricted) return true;
return false;
}
Future<bool> _checkAndRequestLocationPermission() async {
// return true, if already have permission
if (await _checkLocationPermission()) return true;
// request permission
PermissionStatus requestPermission = PermissionStatus.unknown;
int count = 0;
while (requestPermission == PermissionStatus.unknown && count < 20) {
requestPermission = await LocationPermissions().requestPermissions();
printIfDebug("requestPermission: $requestPermission");
await Future.delayed(Duration(seconds: 1));
count++;
}
// check if permission was given
final hasPermission = await _checkLocationPermission();
// if no permission and "showShowPermissionRationale" then go to settings and return false
if (!hasPermission &&
await LocationPermissions().shouldShowRequestPermissionRationale()) {
// if shouldRequest false, then open app settings and return false
// TODO UI that shows why this permission is required
await LocationPermissions().openAppSettings();
return false;
}
return hasPermission;
}
Basically have added a while loop that keeps on requesting until it return unkown status.
The correct behaviour should be that the method returns proper status in one attempt without loop.
Could the bug be because of Acitivyt going inactive/pause when asking for permission causing detachment?
@daadu , I run into the same issue as you.
In my case, I created quickly a prototype of an activity as an experiment.
This activity would check each permission, ask the user to allow it, and if there was any permission that wasn't a allowed, then a dialog would show up. The dialog would explain the user why that permission was needed request again for that permission.
Note: I wouldn't be surprise that there is an error in the code below since I created in a rush Here is my initial code:
class PermissionsActivity : AppCompatActivity() {
companion object {
private val TAG = PermissionsActivity::class.java.simpleName
private const val ACCESS_NETWORK_STATE_CODE : Int = 100
private const val CHANGE_NETWORK_STATE_CODE : Int = 101
private const val ACCESS_INTERNET_CODE : Int = 102
private const val ACCESS_CAMERA_CODE : Int = 103
private const val ACCESS_COARSE_LOCATION_CODE : Int = 104
private const val ACCESS_FINE_LOCATION_CODE : Int = 105
private const val ACCESS_BACKGROUND_LOCATION_CODE: Int = 106
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_permissions)
requestPermissions()
confirmPermissions()
}
private fun requestPermissions(){
if (!hasPermission(Manifest.permission.ACCESS_NETWORK_STATE)) requestAccessNetwork()
if (!hasPermission(Manifest.permission.CHANGE_NETWORK_STATE)) requestChangeNetwork()
if (!hasPermission(Manifest.permission.CAMERA)) requestCamera()
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) requestFineLocation()
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
&& !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) requestCoarseLocation()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (!hasPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) requestBackgroundLocation()
}
}
private fun hasPermission(id: String): Boolean {
return ActivityCompat.checkSelfPermission(this, id) == PackageManager.PERMISSION_GRANTED
}
private fun requestAccessNetwork(){
requestPermission(
Manifest.permission.ACCESS_NETWORK_STATE,
ACCESS_NETWORK_STATE_CODE
)
}
private fun requestPermission(permission: String, code: Int){
ActivityCompat.requestPermissions(
this,
arrayOf(permission),
code
)
}
private fun requestChangeNetwork(){
requestPermission(
Manifest.permission.CHANGE_NETWORK_STATE,
CHANGE_NETWORK_STATE_CODE
)
}
private fun requestCamera(){
requestPermission(
Manifest.permission.CAMERA,
ACCESS_CAMERA_CODE
)
}
private fun requestCoarseLocation(){
requestPermission(
Manifest.permission.ACCESS_COARSE_LOCATION,
ACCESS_COARSE_LOCATION_CODE
)
}
private fun requestFineLocation(){
requestPermission(
Manifest.permission.ACCESS_FINE_LOCATION,
ACCESS_FINE_LOCATION_CODE
)
}
@RequiresApi(Build.VERSION_CODES.Q)
private fun requestBackgroundLocation(){
requestPermission(
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
ACCESS_BACKGROUND_LOCATION_CODE
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray,
) {
Log.d(TAG, "onRequestPermissionsResult()")
when (requestCode) {
ACCESS_NETWORK_STATE_CODE ->
if (wasPermissionGranted(grantResults)) {
changeTintToGreen(R.id.imageViewNetworkAccess)
} else {
showDialog(
"Access Network State Permission",
"Allow access network state permission to talk to the server",
requestAccessNetwork()
)
}
CHANGE_NETWORK_STATE_CODE ->
if (wasPermissionGranted(grantResults)) {
changeTintToGreen(R.id.imageViewNetworkAccess)
} else {
showDialog(
"Change Network State Permission",
"Allow change network state permission to talk to the server",
requestAccessNetwork()
)
}
ACCESS_INTERNET_CODE ->
if (wasPermissionGranted(grantResults)) {
changeTintToGreen(R.id.imageViewInternetAccess)
} else {
showDialog(
"Access Internet Permission",
"Allow access internet permission to talk to the server",
requestAccessNetwork()
)
}
ACCESS_CAMERA_CODE ->
if (wasPermissionGranted(grantResults)) {
changeTintToGreen(R.id.imageViewCameraAccess)
} else {
showDialog(
"Access Camera Permission",
"Allow access camera permission to scan QR barcodes",
requestAccessNetwork()
)
}
ACCESS_FINE_LOCATION_CODE ->
if (wasPermissionGranted(grantResults)) {
changeTintToGreen(R.id.imageViewBackgroundLocationAccess)
} else {
showDialog(
"Access Fine Location Permission",
"Allow access to fine location permission for fine location",
requestAccessNetwork()
)
}
ACCESS_COARSE_LOCATION_CODE ->
if (wasPermissionGranted(grantResults)) {
changeTintToGreen(R.id.imageViewCoarseLocationAccess)
} else {
showDialog(
"Access Coarse Location Permission",
"Allow access to coarse location permission for general location",
requestAccessNetwork()
)
}
ACCESS_BACKGROUND_LOCATION_CODE ->
if (wasPermissionGranted(grantResults)) {
changeTintToGreen(R.id.imageViewNetworkAccess)
} else {
showDialog(
"Access Background Location Permission",
"Allow access to background location permission for fine and general location in Android 10+",
requestAccessNetwork()
)
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun wasPermissionGranted(grantResults: IntArray): Boolean {
return grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
}
private fun changeTintToGreen(id: Int){
findViewById<ImageView>(id).setColorFilter(
ContextCompat.getColor(
this,
R.color.green),
android.graphics.PorterDuff.Mode.MULTIPLY
);
}
private fun showDialog(title: String, message: String, callback: Unit){
AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setPositiveButton("Allow") { _, _ ->
callback
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
setResult(RESULT_CANCELED, Intent())
finish()
}
.create()
.show()
}
private fun confirmPermissions() {
var result = true
result = result and hasPermission(Manifest.permission.ACCESS_NETWORK_STATE)
result = result and hasPermission(Manifest.permission.CHANGE_NETWORK_STATE)
result = result and hasPermission(Manifest.permission.CAMERA)
result = result and hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
result = result and hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
result = result and hasPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
if (result){
setResult(RESULT_OK, Intent())
} else {
setResult(RESULT_CANCELED, Intent())
}
finish()
}
}
Personally, it would be great that the Android API would take care of asking the user for the permissions automatically based on what is in the AndroidManifest.xml
without the developer having to spend time implementing it. It would safe lots of time but I guess that will never be the case.