How to request and check permissions in Flutter
I am using a various plugin to get user data, contact, photos and camera when the user clicks Don't allow, The application goes silent. I want to show the user the ask permission dialog when the user checks the Never ask again and Don't allow and enters the application again.
Currently the app never ask to again when the user checks Don't allow
I Know Users must grant permission for an app to access personal information, including the current location, camera, calendar, contacts, mediaLibrary, microphone, sensors, speech and photos. Although people appreciate the convenience of using an app that has access to this information, they also expect to have control over their private data. For example, people like being able to automatically tag photos with their physical location or find nearby friends, but they also want the option to disable such features.
How to ask for user permission again in flutter?
I was very troubled with this issue After applying several solutions I found this solution. So I want to share it with everyone, that's why I asked the question and I answered
On most operating systems, permissions aren't just granted to apps at install time. Rather, developers have to ask the user for permissions while the app is running.
The best way to handle permissions is by using the permission_handler plugin. This plugin provides a cross-platform (iOS, Android) API to request permissions and check their status.
We can also open the device's app settings so users can grant permission. On Android, we can show a rationale for requesting permission.
-
Add this to your package's
pubspec.yaml
file:dependencies: permission_handler: ^5.0.1+1
-
Now in your Dart code, you can use:
import 'package:permission_handler/permission_handler.dart';
-
While the permissions are being requested during runtime, you'll still need to tell the OS which permissions your app might potentially use. That requires adding permission configuration to Android- and iOS-specific files.
iOS
- Add permission to your Info.plist file. Here's an example Info.plist with a complete list of all possible permissions.
IMPORTANT: You will have to include all permission options when you want to submit your App. This is because the
permission_handler
plugin touches all different SDKs and because the static code analyser (run by Apple upon App submission) detects this and will assert if it cannot find a matching permission option in theInfo.plist
. More information about this can be found here.
-
Add the following to your
Podfile
file:post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## dart: PermissionGroup.calendar 'PERMISSION_EVENTS=0', ## dart: PermissionGroup.reminders 'PERMISSION_REMINDERS=0', ## dart: PermissionGroup.contacts # 'PERMISSION_CONTACTS=0', ## dart: PermissionGroup.camera # 'PERMISSION_CAMERA=0', ## dart: PermissionGroup.microphone # 'PERMISSION_MICROPHONE=0', ## dart: PermissionGroup.speech 'PERMISSION_SPEECH_RECOGNIZER=0', ## dart: PermissionGroup.photos # 'PERMISSION_PHOTOS=0', ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 'PERMISSION_LOCATION=0', ## dart: PermissionGroup.notification # 'PERMISSION_NOTIFICATIONS=0', ## dart: PermissionGroup.mediaLibrary 'PERMISSION_MEDIA_LIBRARY=0', ## dart: PermissionGroup.sensors 'PERMISSION_SENSORS=0' ] end end end
-
Remove the
#
character in front of the permission you do want to use. For example if you do need access to the calendar make sure the code looks like this:## dart: PermissionGroup.calendar 'PERMISSION_EVENTS=0',
Android
-
Add the following to your "
gradle.properties
" file:android.useAndroidX=true android.enableJetifier=true
-
Make sure you set the
compileSdkVersion
in your "android/app/build.gradle
" file to 28:android { compileSdkVersion 30 ... }
-
Make sure you replace all the android. dependencies to their AndroidX counterparts (a full list can be found here: https://developer.android.com/jetpack/androidx/migrate)
Add permissions to your
AndroidManifest.xml
file. There's adebug
,main
andprofile
version which are chosen depending on how you start your app. In general, it's sufficient to add permission only to themain
version. Here's an exampleAndroidManifest.xml
with a complete list of all possible permissions.
Finally you can use like this
There are a number of Permissions. You can get a Permission
's status
, which is either granted
, denied
, restricted
or permanentlyDenied
.
var status = await Permission.photos.status;
if (status.isDenied) {
// We didn't ask for permission yet.
}
// You can can also directly ask the permission about its status.
if (await Permission.location.isRestricted) {
// The OS restricts access, for example because of parental controls.
}
Call `request()` on a `Permission` to request it. If it has already been granted before, nothing happens.
request()
returns the new status of the Permission
.
if (await Permission.contacts.request().isGranted) {
// Either the permission was already granted before or the user just granted it.
}
// You can request multiple permissions at once.
Map<Permission, PermissionStatus> statuses = await [
Permission.location,
Permission.storage,
].request();
print(statuses[Permission.location]);
On Android, you can show a rationale for using permission:
bool isShown = await Permission.contacts.shouldShowRequestRationale;
Full Example
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.camera_enhance),
title: Text(getTranslated(context, "Camera")),
onTap: () async {
var status = await Permission.photos.status;
if (status.isGranted) {
final pickedFile =
await _picker.getImage(source: ImageSource.camera);
final File file = File(pickedFile.path);
imageSelected(file);
} else if (status.isDenied) {
final pickedFile =
await _picker.getImage(source: ImageSource.camera);
final File file = File(pickedFile.path);
imageSelected(file);
} else {
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text('Camera Permission'),
content: Text(
'This app needs camera access to take pictures for upload user profile photo'),
actions: <Widget>[
CupertinoDialogAction(
child: Text('Deny'),
onPressed: () => Navigator.of(context).pop(),
),
CupertinoDialogAction(
child: Text('Settings'),
onPressed: () => openAppSettings(),
),
],
));
}
}),
],
),
)
For simplicity, I used location permission. To request another permission, just replace location with that permission. Here is the list of all permissions.
1. Android setup:
-
Add these to
android/grade.properties
file:android.useAndroidX=true android.enableJetifier=true
-
In
android/app/build.gradle
file:android { compileSdkVersion 30 // Set this to at least 30 ... }
-
Add the permission to
android/app/src/main/AndroidManifest.xml
file<manifest xmlns:android="http://schemas.android.com/apk/res/android" <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> ... </manifest>
2. iOS setup:
-
Add this to
info.plist
file:<key>NSLocationWhenInUseUsageDescription</key> <string>App needs location permission to work</string>
-
Add
PERMISSION_LOCATION=1
toPodfile
:post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## Add the following line. 'PERMISSION_LOCATION=1' ] end end end
3. Flutter setup:
Add this to the pubspec.yaml
file:
permission_handler: ^8.0.0+2
Main work:
-
Check for a permission:
To check if the location aka GPS is on.
final serviceStatus = await Permission.locationWhenInUse.serviceStatus; bool isGpsOn = serviceStatus == ServiceStatus.enabled;
-
Request a permission:
final status = await Permission.locationWhenInUse.request(); if (status == PermissionStatus.granted) { print('Permission granted'); } else if (status == PermissionStatus.denied) { print('Denied. Show a dialog with a reason and again ask for the permission.'); } else if (status == PermissionStatus.permanentlyDenied) { print('Take the user to the settings page.'); }
Full code:
class HomePage extends StatelessWidget {
Future<void> _checkPermission() async {
final serviceStatus = await Permission.locationWhenInUse.serviceStatus;
final isGpsOn = serviceStatus == ServiceStatus.enabled;
if (!isGpsOn) {
print('Turn on location services before requesting permission.');
return;
}
final status = await Permission.locationWhenInUse.request();
if (status == PermissionStatus.granted) {
print('Permission granted');
} else if (status == PermissionStatus.denied) {
print('Permission denied. Show a dialog and again ask for the permission');
} else if (status == PermissionStatus.permanentlyDenied) {
print('Take the user to the settings page.');
await openAppSettings();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: _checkPermission,
child: Text('Check Permission'),
),
),
);
}
}
I recommend using permission_handler
library and write abstract code (strategy pattern) to handle all permissions the same way. Usually, docs are blurry and they do not show how to deal with unrecoverable/disabled scenarios.
Code:
/// handles .isLimited for iOS 14+ where we can restrict access.
abstract class GrantPermissionStrategy {
final Permission permission;
GrantPermissionStrategy(this.permission);
Future<void> request({
required final OnPermanentlyDenied onPermanentlyDenied,
required final OnGranted onGranted,
}) async {
PermissionStatus status = await permission.status;
print("GrantPermissionStrategy status: $status");
if (!status.isLimited && !status.isGranted) {
final PermissionStatus result = await permission.request();
if (result.isPermanentlyDenied) {
onPermanentlyDenied.call();
return;
}
if (!result.isGranted) {
return;
}
}
onGranted.call();
}
}
typedef OnPermanentlyDenied = void Function();
typedef OnGranted = void Function();
And the, you can make concrete implementation, like:
class GrantPermissionCameraStrategy extends GrantPermissionStrategy {
GrantPermissionCameraStrategy() : super(Permission.camera);
}
class GrantPermissionPhotosStrategy extends GrantPermissionStrategy {
GrantPermissionPhotosStrategy() : super(Platform.isAndroid ? Permission.storage : Permission.photos);
}
And finally, invoke it!:
await GrantPermissionPhotosStrategy().request(onPermatentlyDenied: () {
// launch dialog, make user go to app settings
}, onGranted: () async {
// we have passed! Launch the feature.
});
}