π§ smart_permission
π An opinionated wrapper around permission_handler that makes runtime permissions effortless.
Request permissions with one API β smart dialogs, adaptive styles, and complete UX flow handling built-in.
β¨ Why smart_permission?
Most apps spend unnecessary time handling permission logic manually.
smart_permission takes care of the entire flow β from first ask to permanently denied β automatically, with adaptive dialogs and analytics tracking.
π‘ What makes it different?
- β One-line permission requests (single or batch)
- β Handles denied, permanently denied, and restricted flows automatically
- β
Rich results (
SmartPermissionResult) that tell you why a request failed - β Waits for the user to return from Settings before re-checking
- β Adaptive Material / Cupertino / Adaptive dialogs
- β Built-in titles & descriptions (with custom overrides) + full localization
- β Global theming, analytics hooks, and custom dialogs
- β Testable: inject a fake gateway to widget-test your permission UX
- β
Re-exports
Permissionβ no need for multiple imports
π§© Feature Highlights
| Feature | Description |
|---|---|
| π Easy API | SmartPermission.request() for one or many permissions |
| π― Rich Results | requestResult() returns granted/denied/permanentlyDenied/... |
| π¨ Adaptive Dialogs | Material / Cupertino / Platform adaptive support |
| π§ Auto Flows | Denied, permanently denied, and restricted handled automatically |
| π¬ Rationale First | Optionally explain before the one-shot native prompt |
| π Localization | Every built-in string replaceable via SmartPermissionStrings |
| π§± Central Configuration | Global themes, titles, descriptions, analytics, navigator key |
| π§© Custom Dialog Builders | Build your own dialog UI if you need full control |
| π Analytics Hooks | requested / granted / denied / permanently denied / restricted |
| π§ͺ Test-Friendly | Swap the platform gateway with a fake in widget tests |
π₯οΈ Platform Support
| Android | iOS | Web (incl. WASM) | Windows | macOS | Linux |
|---|---|---|---|---|---|
| β | β | β | β | β | β |
macOS and Linux are not supported because the underlying
permission_handler plugin has
no implementation for them.
βοΈ Installation
Add to your pubspec.yaml:
dependencies:
smart_permission: ^1.0.0
Android note:
smart_permission1.0.0+ depends onpermission_handler13, which requires your app to build with compileSdk 37. Flutter's default is still 36, so set it explicitly inandroid/app/build.gradle(.kts):android { compileSdk = 37 // ... }
Then import:
import 'package:smart_permission/smart_permission.dart';
β‘ Quick Start Example
final ok = await SmartPermission.request(
context,
permission: Permission.camera,
style: PermissionDialogStyle.adaptive,
description: 'We need camera to scan QR codes.',
);
Or request multiple:
final result = await SmartPermission.requestMultiple(
context,
permissions: [
Permission.camera,
Permission.microphone,
],
);
Automatic Dialog Flow
| State | Behavior |
|---|---|
| First time | Shows native system sheet (optionally after a rationale) |
| Denied | Shows rationale dialog with retry |
| Permanently denied | Shows βOpen Settingsβ dialog, waits for return, re-checks |
| Restricted | Shows an informational dialog (settings can't fix restricted) |
Need to know why it failed? Use the result API
final result = await SmartPermission.requestResult(
context: context,
permission: Permission.camera,
);
if (result.canProceed) {
// granted, limited, or provisional
openCamera();
} else if (result == SmartPermissionResult.permanentlyDenied) {
// Show a settings shortcut somewhere in your UI.
showSettingsShortcut();
} else if (result == SmartPermissionResult.restricted) {
// Blocked by the OS (e.g. parental controls) β hide the feature.
hideCameraFeature();
} else {
// denied or error β you can ask again later.
}
requestMultipleResults does the same for batches β one native batch flow,
one combined rationale dialog, and one combined settings dialog instead of a
dialog per permission.
Explain before the native prompt (recommended)
On iOS you only get one native ask β explain first:
// Per call:
await SmartPermission.request(context,
permission: Permission.camera, showRationaleFirst: true);
// Or globally:
SmartPermission.config.showRationaleBeforeRequest = true;
No BuildContext? Use a navigator key
final navKey = GlobalKey<NavigatorState>();
MaterialApp(navigatorKey: navKey, ...);
SmartPermission.config.navigatorKey = navKey;
// Anywhere, without a context:
final result = await SmartPermission.requestResult(
permission: Permission.camera,
);
π¨ Configuration & Customization
You can configure SmartPermission globally at app startup or dynamically at runtime.
Global Setup
SmartPermission.config
..brightness = Brightness.light
..primaryColor = Colors.indigo
..analytics = InMemoryPermissionAnalyticsTracker();
Toggle Theme & Primary Color (as in example app)
SmartPermission.config.brightness =
isDark ? Brightness.dark : Brightness.light;
SmartPermission.config.primaryColor = selectedPrimaryColor;
π§ Custom Texts Per Permission
Provide your own titles and messages for each permission:
SmartPermission.config
..titleProvider = (p) {
if (p == Permission.camera) return 'Camera Access Needed';
if (p == Permission.microphone) return 'Microphone Access Needed';
if (p == Permission.locationWhenInUse) return 'Location Access Needed';
return null;
}
..descriptionProvider = (p) {
if (p == Permission.camera) return 'We need the camera to scan QR codes.';
if (p == Permission.microphone) return 'We need the microphone for voice features.';
if (p == Permission.locationWhenInUse) return 'We use your location to show nearby stores.';
return null;
};
π§© Custom Dialog Builder
Want your own UI (like a bottom sheet)? You can override the default dialog completely.
SmartPermission.config.customDialogBuilder = (
context, {
required style,
required title,
required message,
required primaryText,
required secondaryText,
}) async {
return showModalBottomSheet<bool>(
context: context,
isScrollControlled: true,
backgroundColor: Theme.of(context).colorScheme.surface,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (ctx) {
final cs = Theme.of(ctx).colorScheme;
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(children: [
Icon(Icons.privacy_tip_outlined, color: cs.primary),
const SizedBox(width: 12),
Expanded(child: Text(title, style: Theme.of(ctx).textTheme.titleLarge)),
]),
const SizedBox(height: 12),
Text(message),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text(secondaryText),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: Text(primaryText),
),
),
],
),
],
),
);
},
);
};
π Localization
Every built-in string can be replaced (unset ones keep their English default):
SmartPermission.config.strings = const SmartPermissionStrings(
allow: 'Autoriser',
notNow: 'Plus tard',
openSettings: 'Ouvrir les rΓ©glages',
blockedMessage: 'Autorisation dΓ©sactivΓ©e. Activez-la dans les rΓ©glages.',
);
Combine with titleProvider/descriptionProvider for localized
per-permission texts.
π Analytics Tracking
Track user behavior for better insights or debugging. Extend
PermissionAnalyticsTracker (don't implement it) so future hooks won't break
your tracker:
class MyAnalytics extends PermissionAnalyticsTracker {
@override
void onRequested(Permission permission) => log('requested: $permission');
@override
void onGranted(Permission permission) => log('granted: $permission');
@override
void onDenied(Permission permission) => log('denied: $permission');
@override
void onPermanentlyDenied(Permission permission) => log('blocked: $permission');
@override
void onRestricted(Permission permission) => log('restricted: $permission');
}
SmartPermission.config.analytics = MyAnalytics();
The example app uses a small tracker to log permission denials in real-time within the UI.
π¨ Error Reporting
Platform-call failures are logged with debugPrint and surface as
SmartPermissionResult.error. Forward them to your crash reporter:
SmartPermission.config.onError = (error, stack) =>
FirebaseCrashlytics.instance.recordError(error, stack);
π§ͺ Testing Your Permission Flows
Widget-test your app's permission UX without platform channels by swapping the gateway:
class FakeGateway implements SmartPermissionGateway {
// Return whatever statuses your test needs...
}
SmartPermission.config.gateway = FakeGateway();
// ...run your flow, then:
SmartPermission.config.resetToDefaults();
π§© Example App Features
The included /example project demonstrates:
- π Theme toggle (Light/Dark)
- π¨ Primary color cycling (Indigo / Teal / Orange)
- π± Dialog style selection (Material, Cupertino, Adaptive)
- π§ Custom title/description per permission
- πͺ Custom bottom-sheet dialog builder
- π Real-time analytics tracking for denied states
- π¦ Batch permission requests
π§ Tips & Notes
- Some Android permissions (e.g.,
manageExternalStorage,systemAlertWindow) open system settings instead of a dialog. - Android 17 adds
Permission.accessLocalNetwork(declareandroid.permission.ACCESS_LOCAL_NETWORKin your manifest) β supported out of the box with built-in dialog texts. - On Android 13+, use
Permission.photos,Permission.videos, orPermission.audioinstead of deprecated storage permissions. locationAlwaysoften requires first grantinglocationWhenInUse.- Use
SmartPermission.requestMultiple()for grouped requests.
π Migrating from 0.x
1.0.0 keeps the 0.x API working β request(...) and requestMultiple(...)
have the same signatures and return types. Notes:
- Android:
permission_handler13 requires building withcompileSdk 37. - Analytics: if your tracker used
implements PermissionAnalyticsTracker, switch toextends(new hooks were added:onRequested,onGranted,onRestricted). - Behavior:
requestMultiplenow runs one native batch flow with combined dialogs instead of a full dialog flow per permission; the settings flow now waits for the user to return from Settings before re-checking (previously it re-checked immediately and usually reportedfalse). - New APIs (opt-in):
requestResult,requestMultipleResults,SmartPermissionResult,SmartPermissionStrings,showRationaleFirst,config.navigatorKey,config.onError,config.gateway.
π€ Author
Created with β€οΈ by Jaimin Kavathia πΌ LinkedIn β’ π GitHub
π License
Licensed under the MIT License. Free for personal and commercial use.
β If you like this package, give it a star on GitHub & pub.dev!
smart_permission β because handling permissions shouldnβt be a hassle.