smart_permission 1.0.0 copy "smart_permission: ^1.0.0" to clipboard
smart_permission: ^1.0.0 copied to clipboard

Flutter runtime permissions made easy: one-line requests, adaptive dialogs, and full flows on Android & iOS.

🧠 smart_permission #

Pub.dev Badge Build Badge MIT License Flutter Badge

πŸš€ 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.

Smart Permission


✨ 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 and permanently denied flows automatically
  • βœ… Adaptive Material / Cupertino / Adaptive dialogs
  • βœ… Built-in titles & descriptions (with custom overrides)
  • βœ… Global theming, analytics hooks, and custom dialogs
  • βœ… Re-exports Permission β€” no need for multiple imports

🧩 Feature Highlights #

Feature Description
πŸ” Easy API SmartPermission.request() for one or many permissions
🎨 Adaptive Dialogs Material / Cupertino / Platform adaptive support
🧭 Auto Flows Handles denied/permanently denied states automatically
🧱 Central Configuration Set global themes, titles, descriptions, analytics
🧩 Custom Dialog Builders Build your own dialog UI if you need full control
🧠 In-Memory Analytics Tracker Tracks denied and permanently denied permissions

πŸ–₯️ 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_permission 1.0.0+ depends on permission_handler 13, which requires your app to build with compileSdk 37. Flutter's default is still 36, so set it explicitly in android/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,
);

switch (result) {
  case SmartPermissionResult.granted:
  case SmartPermissionResult.limited:
  case SmartPermissionResult.provisional:
    openCamera(); // or just check result.canProceed
  case SmartPermissionResult.permanentlyDenied:
    // Show a settings shortcut somewhere in your UI.
  case SmartPermissionResult.restricted:
    // Blocked by the OS (e.g. parental controls).
  case SmartPermissionResult.denied:
  case SmartPermissionResult.error:
    // Try 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.

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 (declare android.permission.ACCESS_LOCAL_NETWORK in your manifest) β€” supported out of the box with built-in dialog texts.
  • On Android 13+, use Permission.photos, Permission.videos, or Permission.audio instead of deprecated storage permissions.
  • locationAlways often requires first granting locationWhenInUse.
  • 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_handler 13 requires building with compileSdk 37.
  • Analytics: if your tracker used implements PermissionAnalyticsTracker, switch to extends (new hooks were added: onRequested, onGranted, onRestricted).
  • Behavior: requestMultiple now 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 reported false).
  • 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.

18
likes
160
points
1.4k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter runtime permissions made easy: one-line requests, adaptive dialogs, and full flows on Android & iOS.

Repository (GitHub)
View/report issues

Topics

#permission #runtime-permissions #permission-handler #adaptive-dialog #app-settings

License

MIT (license)

Dependencies

flutter, permission_handler

More

Packages that depend on smart_permission