simple_permissions_native 1.6.0
simple_permissions_native: ^1.6.0 copied to clipboard
Unified permission handling for Flutter apps. Manages runtime permissions, app roles (SMS, Dialer), and battery optimization.
simple_permissions_native #
A federated Flutter permission plugin with a typed, sealed-class API. Every permission is a Dart type — no stringly-typed lookups, no magic enums. Pattern-match on results, batch requests in a single call, and express features as Intentions instead of raw permission lists.
Backed by Pigeon-generated native bridges for Android (Kotlin), iOS (Swift), and macOS (Swift).
Installation #
dependencies:
simple_permissions_native: ^1.2.0
Quick start #
import 'package:simple_permissions_native/simple_permissions_native.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SimplePermissionsNative.initialize();
runApp(const MyApp());
}
Check and request a single permission #
final perms = SimplePermissionsNative.instance;
final grant = await perms.check(const CameraAccess());
if (grant != PermissionGrant.granted) {
final result = await perms.request(const CameraAccess());
if (result == PermissionGrant.permanentlyDenied) {
// User selected "Don't allow" — direct them to Settings
await perms.openAppSettings();
}
}
Batch-request multiple permissions #
final result = await perms.requestAll([
const CameraAccess(),
const RecordAudio(),
const FineLocation(),
]);
if (result.isFullyGranted) {
startVideoCall();
} else if (result.requiresSettings) {
showSettingsPrompt(result.permanentlyDenied);
}
Use Intentions for feature-level requests #
Instead of listing individual permissions, express what the app intends to do:
// Request everything needed for SMS messaging
final ok = await perms.requestIntention(Intention.texting);
// Or get detailed results
final result = await perms.requestIntentionDetailed(Intention.contacts);
if (result.hasDenial) {
handleDenied(result.denied);
}
Compose custom Intentions:
final videoCall = Intention.combine('video_call', [
Intention.camera,
Intention.microphone,
Intention.location,
]);
Version-aware permissions #
Android splits storage permissions at API 33 and Bluetooth at API 31. VersionedPermission resolves this automatically:
// Resolves to ReadMediaImages on API 33+, ReadExternalStorage on older devices
final result = await perms.request(VersionedPermission.images());
API reference #
| Method | Returns | Description |
|---|---|---|
initialize() |
Future<void> |
Required before any other call |
check(Permission) |
Future<PermissionGrant> |
Current grant state (no prompt) |
request(Permission) |
Future<PermissionGrant> |
Request with system dialog |
checkAll(List<Permission>) |
Future<PermissionResult> |
Batch check |
requestAll(List<Permission>) |
Future<PermissionResult> |
Batch request (optimized on Android) |
checkIntention(Intention) |
Future<bool> |
All permissions in intention granted? |
requestIntention(Intention) |
Future<bool> |
Request all, return success/failure |
checkIntentionDetailed(Intention) |
Future<PermissionResult> |
Intention with per-permission results |
requestIntentionDetailed(Intention) |
Future<PermissionResult> |
Request intention with details |
isSupported(Permission) |
Future<bool> |
Does this permission exist on this platform/OS version? |
openAppSettings() |
Future<bool> |
Open system settings for this app |
checkLocationAccuracy() |
Future<LocationAccuracyStatus> |
GPS precision level |
PermissionGrant states #
| State | Meaning | What to do |
|---|---|---|
granted |
User approved | Proceed |
denied |
User denied (can ask again) | Show rationale, then re-request |
permanentlyDenied |
"Don't ask again" selected | Call openAppSettings() |
restricted |
OS-level restriction (parental controls, MDM) | Inform user; cannot be changed |
limited |
Partial access (e.g., iOS limited photo library) | Usable — isFullyGranted treats this as satisfied |
provisional |
iOS provisional notifications (delivers quietly) | Usable — isFullyGranted treats this as satisfied |
notApplicable |
Permission doesn't exist on this platform | Hide the feature or skip |
notAvailable |
Permission exists but not on this OS version | Hide or use fallback |
Intentions #
Built-in presets group common runtime permissions. Role takeovers are explicit:
| Intention | Permissions |
|---|---|
Intention.texting |
SendSms, ReadSms, ReceiveSms, ReceiveMms, ReceiveWapPush |
Intention.calling |
ReadPhoneState, ReadPhoneNumbers, MakeCalls, AnswerCalls |
Intention.contacts |
ReadContacts, WriteContacts |
Intention.camera |
CameraAccess |
Intention.microphone |
RecordAudio |
Intention.location |
FineLocation, CoarseLocation |
Intention.notifications |
PostNotifications |
Intention.mediaImages |
VersionedPermission.images() |
Intention.mediaVideo |
VersionedPermission.video() |
Intention.mediaAudio |
VersionedPermission.audio() |
Intention.mediaVisual |
images + video combined |
Intention.defaultSmsRole |
DefaultSmsApp (Android role) |
Intention.defaultDialerRole |
DefaultDialerApp (Android role) |
Intention.textingWithDefaultSmsRole and Intention.callingWithDefaultDialerRole combine runtime permissions with role requests.
Platform support #
| Platform | Support |
|---|---|
| Android | Runtime permissions, app roles, system-setting intents |
| iOS | Framework permission APIs (22 permissions) |
| macOS | Framework permission APIs (13 permissions) |
| Web | Camera, microphone, geolocation, notifications via browser Permissions API |
| Linux / Windows | Returns notApplicable (graceful no-op) |
Host app setup #
iOS — Info.plist #
Add only the usage-description keys your app needs. The App Store will reject builds that include unused keys.
| Key | Required for |
|---|---|
NSContactsUsageDescription |
ReadContacts, WriteContacts |
NSCameraUsageDescription |
CameraAccess |
NSMicrophoneUsageDescription |
RecordAudio |
NSLocationWhenInUseUsageDescription |
FineLocation, CoarseLocation |
NSLocationAlwaysAndWhenInUseUsageDescription |
BackgroundLocation |
NSPhotoLibraryUsageDescription |
ReadMediaImages, ReadMediaVideo |
NSCalendarsUsageDescription |
ReadCalendar, WriteCalendar |
NSRemindersUsageDescription |
ReadReminders, WriteReminders |
NSSpeechRecognitionUsageDescription |
SpeechRecognition |
NSBluetoothAlwaysUsageDescription |
BluetoothConnect, BluetoothScan, BluetoothAdvertise |
NSMotionUsageDescription |
BodySensors, ActivityRecognition |
NSUserTrackingUsageDescription |
AppTrackingTransparency |
NSHealthShareUsageDescription |
HealthAccess |
NSHealthUpdateUsageDescription |
HealthAccess |
macOS — Entitlements #
macOS sandboxed apps require matching entitlements in addition to Info.plist keys:
<!-- Example: enable camera + contacts in *.entitlements -->
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.personal-information.addressbook</key>
<true/>
Android — AndroidManifest.xml #
Declare permissions in android/app/src/main/AndroidManifest.xml. Only include what your app uses.
<!-- Common permissions -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Storage: use both for backward compatibility -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- Notifications (API 33+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
See the full list of Android permission strings in the Android developer docs.
Native Kotlin helpers for sibling plugins #
Plugin authors whose Android code needs to check (read-only) whether a
runtime permission is granted or whether the app holds a default-app role can
import PermissionGuards from simple_permissions_android instead of
reaching for ContextCompat.checkSelfPermission(...) / RoleManager.isRoleHeld(...)
directly.
import io.simplezen.simple_permissions_android.PermissionGuards
import android.app.role.RoleManager
import android.Manifest
// Single permission
if (!PermissionGuards.isPermissionGranted(
context, Manifest.permission.READ_SMS)) {
return emptyList() // silent-fail path: caller must request via Dart API
}
// Batch precondition
if (!PermissionGuards.areAllPermissionsGranted(
context,
listOf(Manifest.permission.READ_SMS, Manifest.permission.SEND_SMS))) {
return
}
// Default-app role
if (!PermissionGuards.isRoleHeld(context, RoleManager.ROLE_SMS)) {
// Read-only; to request the role, call
// SimplePermissionsNative.instance.request(DefaultSmsApp()) from Dart.
}
Gradle wiring #
Flutter's plugin-loader is what makes cross-repo consumption work, and it does exactly the right thing once you give it the right inputs:
Consuming plugin's root pubspec.yaml declares the Dart dep:
dependencies:
simple_permissions_native: ^1.4.0
Consuming plugin's android/build.gradle[.kts] wires the project ref:
dependencies {
implementation(project(":simple_permissions_android"))
}
That's it. In any app that consumes the plugin, Flutter's plugin-loader
walks the pubspec graph — including transitive plugin deps — and
synthesizes Gradle project entries for each federated plugin's Android
module. The :simple_permissions_android project then exists in the
final app's Gradle build alongside your consuming plugin, and the
implementation(project(...)) ref resolves.
No composite builds, no Maven publication, no settings.gradle gymnastics.
One gotcha for local path-dep workflows
If your consuming plugin's example app uses dependency_overrides to
point at a sibling simple-permissions checkout, override each federated
platform package explicitly — not just the facade:
dependency_overrides:
simple_permissions_native:
path: ../../simple-permissions
simple_permissions_platform_interface:
path: ../../simple-permissions/packages/simple_permissions_platform_interface
simple_permissions_android:
path: ../../simple-permissions/packages/simple_permissions_android
# ...ios, macos, web if you need them
Why: the plugin-loader registers Gradle project paths per federated
platform package. If only the facade is overridden, :simple_permissions_android
silently resolves to whatever's on pub.dev, which will be older than your
local working copy — and Kotlin compile fails with "unresolved reference"
on any new native API. Overriding the platform packages too keeps the
Gradle classpath and the Dart workspace in lockstep.
When none of the above apply
If you can't add simple_permissions_native to the consuming plugin
(e.g. you want to avoid the runtime dep for policy reasons), keep using
Android primitives inline — ContextCompat.checkSelfPermission(...) is a
legitimate read-only OS query, and Rule 2 ("access state goes through
simple-permissions") is still upheld at the Dart API boundary
(request flows, observation, permission types). PermissionGuards is a
cleanup path, not a requirement.
No request-side helpers #
PermissionGuards deliberately does not expose a "request permission"
equivalent. Request flows surface UI and route through activity bindings —
they belong behind the Dart API (SimplePermissionsNative.instance.request(...))
so the prompt is scoped by the user's consent flow, not a random native call
site.
Architecture #
simple_permissions_native/
├── lib/simple_permissions_native.dart <- App-facing facade
├── packages/simple_permissions_platform_interface/
│ └── Permission sealed classes, PermissionGrant, PermissionResult, Intention
├── packages/simple_permissions_android/ <- Pigeon + Kotlin handlers
├── packages/simple_permissions_ios/ <- Pigeon + Swift handlers
├── packages/simple_permissions_macos/ <- Pigeon + Swift handlers
└── packages/simple_permissions_web/ <- Browser Permissions API
License #
MIT © 2025 SimpleZen