simple_permissions_native 1.6.4
simple_permissions_native: ^1.6.4 copied to clipboard
Unified permission handling for Flutter apps. Manages runtime permissions, app roles (SMS, Dialer), and battery optimization.
1.6.4 #
-
Require Flutter 3.44 / Dart 3.12, the floor of the platform packages' Swift Package Manager and built-in Kotlin support (UNFY-173).
-
Keep Flutter's build/platform analyzer exclusions explicit so dependency resolution leaves publishable source unchanged on current stable Flutter (UNFY-123).
-
Require platform packages
^1.3.4so consumers resolve compatible published dependencies, including Android's nativePermissionGuardsAPI (UNFY-123). -
Validate published dependencies before tagging and publish Flutter packages with normal validation and temporary OIDC credentials (UNFY-123).
-
ci(UNFY-197): fail the native gate if no unit tests ran; correct NDK comments
-
Merge origin/main into debt/unfy-197-native-unit-test-gate
-
fix(UNFY-173): keep explicit initializers for private fields
-
chore(UNFY-173): declare the example's Flutter 3.47 minimum
-
chore(UNFY-173): record the migration in changelogs; build the example
-
Merge remote-tracking branch 'origin/main' into claude/keen-fermi-n6kg9p
-
chore(UNFY-173): raise the facade's SDK floor to Flutter 3.44 / Dart 3.12
-
chore(UNFY-173): adopt Swift Package Manager and built-in Kotlin
-
chore(UNFY-197): bump example Android toolchain to Flutter stable minimums
-
fix(UNFY-345): scope release validation and route execution docs
-
fix(UNFY-345): preserve quota cooldowns and reject authored overrides
-
Reconcile cancelled deployments and verify complete release archives (UNFY-345)
-
Coordinate affected plugin releases and resume quota pauses (UNFY-345)
-
fix(UNFY-123): keep Flutter analyzer migration source clean
-
fix(UNFY-123): validate and release compatible permissions dependencies
-
chore(UNFY-271): gitignore .exec/ executor workdirs
-
chore(unfy-97): retire docs/adr + docs/runbooks trees
-
docs: address remaining PR #60 Sourcery comment
-
docs: address PR #60 review comments
-
docs: add Simple Zen standard scaffolding (AGENTS canonical + CLAUDE @import, adr/runbooks)
-
chore(release): cut 6 tags (patch bump)
-
chore(release): cut 6 tags (patch bump)
-
chore(release): reset versions to align with pub.dev
-
chore: gitignore + untrack desktop generated plugin registrants
-
chore(release): align federated packages on coordinated 1.8.0
-
Merge pull request #26 from jake-dev-99/release/1.8.0-rc.1
-
docs + chore: re-frame PermissionGuards as developer-facing; bump 1.8.0
-
test(ios): XCTest suite for PermissionGuards
-
fix(ci): auto-tag pushes via deploy key to satisfy main ruleset
-
Merge pull request #24 from jake-dev-99/release/1.7.0-rc.1
-
chore: bump to 1.7.0 + CHANGELOG entry
-
docs(integration): real iOS/macOS worked example using PermissionGuards
-
refactor(observer): use cleanup holder as finalizer detach token; unawaited close
-
Require simple_permissions_platform_interface ^1.3.4.
1.6.3 #
- Merge pull request #59 from jake-dev-99/claude/happy-austin-bc06a5
- fix(release): split push so tag events trigger Deploy workflow
1.6.2 #
- fix(release): use static github-actions[bot] identity
- fix(release): use vars.RELEASE_APP_ID instead of secrets
- fix(release): use GitHub App token for signed commits
- fix(release): repair YAML parse error from multi-line bash literal
- fix(deploy): use dart pub publish for OIDC handshake
- fix(cicd): address rename-PR review (security + cosmetic + docs)
- ci(workflows): rename for human-friendly UI labels (Verify / Deploy)
- ci: defensive fixes from bridge review (deep main fetch + reachable prev_tag)
- ci(critical): tag-on-main verify, SHA-pin OIDC actions, push-on-main trigger, empty-changes guard
- ci(publish): revert to proven subosito/flutter-action + flutter pub publish
- ci(cd): single-branch + tag-driven publish (overhaul)
1.6.1 #
Reset to align with pub.dev's published series #
simple_permissions_native on pub.dev is at 1.6.0. Local git
tags 1.7.0, 1.8.0, 1.8.1, 1.8.2 were authored ahead of the next
publish but never reached pub.dev (publish runs failed; tags
were orphans). All four orphan tags have been deleted from
origin and the version reset to the smallest patch above the
last-published version (1.6.0 → 1.6.1) so the next auto-tag
cycle produces a published artifact users can actually
flutter pub get.
Cross-package constraints lowered from ^1.8.0 to ^1.3.0 to
match the platform packages' published versions (also reset to
1.3.1 in this PR).
The 1.7.0 / 1.8.0 work described in the entries below is not lost — it lives in source and will surface in published form through subsequent patch / minor / major bumps as appropriate. Entries kept for archival reference.
1.8.0 #
Changed — PermissionGuards re-framed as a developer-facing helper #
The 1.7.0 notes framed Apple PermissionGuards as defense-in-depth for sibling Flutter plugins using a "three-layer responsibility model." That framing is superseded: sibling plugins should stay independent and not embed simple_permissions_native. Client-app developers are responsible for permission management, typically via SimplePermissionsNative.instance.ensureGranted(...) on the Dart side, before invoking any sibling-plugin API.
PermissionGuards itself continues to exist — now scoped to your own native iOS / macOS / Android code (app extensions, native UI, AppDelegate work, native modules you own). The API is strictly additive from 1.7 to 1.8; the rewrite is documentation + scope, not code deletion.
docs/INTEGRATION_GUIDE.md fully rewritten for the new framing.
Added — Apple PermissionGrant + authorizationStatus(for:) #
Swift-side mirror of Dart's 8-case PermissionGrant enum with the same isSatisfied / isDenied / isUnsupported / isTerminal predicates. Raw string values ("granted", "denied", …) match the Dart wire format 1:1.
PermissionGuards.authorizationStatus(for:) -> PermissionGrant— primary new query; lets callers distinguish denied vs permanentlyDenied vs restricted vs limited vs notAvailable instead of collapsing intoBool.isAuthorized(for:)rebuilt on top:authorizationStatus(for: kind).isSatisfied. Existing callers unchanged.notificationsStatus() async -> PermissionGrantfor parity.
iOS and macOS modules get identical API. Pre-iOS-13 bluetooth and pre-iOS-14 tracking now report .notAvailable explicitly (previously implicit .granted). iOS 18+ CNAuthorizationStatus.limited and iOS-17+ EKAuthorizationStatus.fullAccess / .writeOnly mapped to appropriate grants.
Added — Apple async request helpers #
Sync check + async request, mirrored on iOS and macOS.
PermissionGuards.requestAuthorization(for:) async -> PermissionGrant— triggers the system prompt (on first use) and returns the post-prompt grant. Short-circuits without prompting when already satisfied or in a terminal state.PermissionGuards.requireAuthorizationGranted(for:) async throws— throwing counterpart.PermissionGuards.requestNotificationsAuthorization(options:) async -> PermissionGrant— notifications-specific (async-only framework API).PermissionGuards.requireNotificationsAuthorizationGranted(options:) async throws— throwing notifications counterpart.
Per-kind wiring uses each framework's native async API where one exists (iOS 14+ photo library, iOS 15+ EventKit full-access, iOS 14+ ATTrackingManager), falls back to completion-wrapping via withCheckedContinuation on older OS versions. Delegate-based frameworks (CoreLocation, CoreBluetooth) use one-shot coordinator classes marked @unchecked Sendable with a documented invariant.
Added — .health(HKObjectType) case (iOS only) #
HealthKit gating. Associated-value enum case so callers supply the specific HKObjectType they need:
let stepType = HKObjectType.quantityType(forIdentifier: .stepCount)!
try await PermissionGuards.requireAuthorizationGranted(for: .health(stepType))
Apple privacy caveat documented inline: HKHealthStore.authorizationStatus(for:) only reflects write authorization — read is opaque by design. Request path asks for both share (HKSampleType only) and read where the type supports it, so the user sees one consolidated prompt. iOS 15+ uses the native async API; 14 falls back to the completion-based variant.
macOS deliberately excluded from this stream — HealthKit on macOS is rare; revisit if a macOS consumer materializes.
Added — XCTest suite for PermissionGuards #
example/ios/RunnerTests/RunnerTests.swift gets five test classes / ~20 tests covering the deterministic parts of PermissionGuards:
PermissionGrantTests— rawValue wire-format stability, predicate matrices, mutual exclusivity, exactly-one-category coverage across all 8 cases.ApplePermissionKindTests— identifier stability for all zero-arg cases +.healthformatting.PermissionDeniedErrorTests— construction, description, order preservation.PermissionGuardsRequireTests— empty-collection edge cases (requireAny throws, requireAll vacuously succeeds), error shapes.AuthorizationStatusTests— every kind round-trips through PermissionGrant rawValue, invariant thatisAuthorized≡authorizationStatus.isSatisfied, HealthKit-unavailable graceful handling.
Locally-runnable via xcodebuild test -scheme Runner. CI wiring is a separate future-work item.
Package versions #
simple_permissions_native1.7.0 → 1.8.0simple_permissions_ios1.3.0 → 1.4.0 (podspec aligned)simple_permissions_macos1.3.0 → 1.4.0 (podspec aligned)- Android + platform-interface + web unchanged.
Explicitly deferred #
- macOS
.healthcase — no consumer. Revisit when one exists. - Simulator-based integration tests via
xcrun simctl privacy— future work. - CI wiring for the XCTest target — depends on macOS runner minutes budget.
1.7.0 #
Added — Apple native assertions (PermissionGuards for iOS + macOS) #
Swift companion to Kotlin's PermissionGuards (landed 1.6.0). Sibling Flutter plugins with Swift code that invokes permission-gated Apple frameworks can now throw a clear library-specific domain error instead of leaking whatever opaque status the framework returns on unauthorized.
iOS — simple_permissions_ios 1.3.0 (+ podspec 1.3.0):
ApplePermissionKindenum — 12 cases:.contacts,.camera,.microphone,.calendar,.reminders,.photoLibrary,.photoLibraryAddOnly,.location,.speech,.tracking,.motion,.bluetooth. Each delegates to the exact framework call already in use by the corresponding*PermissionHandler.swift.PermissionGuards.isAuthorized(for:)— read-onlyBool.PermissionGuards.requireAuthorized(for:)/requireAnyAuthorized(for:)/requireAllAuthorized(for:)— throwPermissionDeniedErrorwith.deniedPermissions: [String]listing the missing kinds (Anylists all when all deny;Alllists only the missing subset).PermissionGuards.isNotificationsAuthorized()/requireNotificationsAuthorized()—asyncvariants, becauseUNUserNotificationCenter.getNotificationSettingsis callback-only. Treats.authorized,.provisional,.ephemeralas authorized (matches the DartPermissionGrantStatus.isSatisfiedmapping).PermissionDeniedError: Error, CustomStringConvertible— Apple analog of Kotlin'sPermissionDeniedException.
macOS — simple_permissions_macos 1.3.0 (+ podspec 1.3.0):
MacOSPermissionKindenum — 8-case subset:.contacts,.camera,.microphone,.calendar,.reminders,.photoLibrary,.photoLibraryAddOnly,.location.- Same helper shape as iOS (
isAuthorized/require*/ async notifications /PermissionDeniedError). - Platform-specific implementations where Apple's APIs diverge: microphone uses
AVCaptureDevice.authorizationStatus(for: .audio)(iOS usesAVAudioSession.recordPermission); location and photo library have#available(macOS 11.0, *)fallbacks to the pre-11 class-method shapes.
Integration guide #
- Replaced the placeholder iOS/macOS section in
docs/INTEGRATION_GUIDE.mdwith a worked example using the new helpers. Includes a covered-kinds list per platform, a framework-contract → helper picker table, theInfo.plistusage-description note (Apple equivalent of Android's<service android:permission>split), and one-line adoption instructions (s.dependency 'simple_permissions_ios'+import simple_permissions_ios). - Shippable-plugin checklist updated with the Swift-side items and the Info.plist responsibility split.
Explicitly deferred #
.health— HealthKit gating.HKHealthStore.authorizationStatus(for:)is parameterized byHKObjectType, so a single case can't carry the type. Sibling plugins needing HealthKit call the framework directly with a domain wrapper. Will grow a case when a concretesimple-healthplugin materializes.- XCTest coverage for Swift guards. The iOS and macOS plugin modules have never had a test target; standing one up is scoped out of this PR. Sibling-plugin adoption (simple-telephony iOS first) is the primary verification.
No Dart changes #
Purely additive native API. No platform-interface changes, no facade changes; simple_permissions_native bumps from 1.6.0 → 1.7.0 as the version-bundle convention.
1.6.0 #
Added — runtime gate helpers (Dart facade) #
SimplePermissionsNative.instance.ensureGranted(Permission)— checks first; requests only if not satisfied and not in a terminal state (permanentlyDenied / restricted / notApplicable / notAvailable). Returns the post-request grant so the caller can branch on denial mode.ensureGrantedAll(List<Permission>)/ensureIntention(Intention)— batch forms. Only the prompt-worthy permissions are forwarded torequestAll; the rest are pulled from the initial check snapshot. Avoids re-prompting permissions the user has permanently denied.guard<T>(Permission, Future<T> Function())/guardAll/guardIntention— run-with sugar. Returns the action's value if granted,nullotherwise. UseensureGrantedwhen you need to distinguish why a guard returned null (e.g. to route toopenAppSettings).- All new methods compose existing
check/request/checkAll/requestAll— no platform-interface changes.
Added — Android native assertions (PermissionGuards) #
PermissionGuards.requirePermissionGranted(context, permission)— throwsPermissionDeniedException(extendsSecurityException) if the permission isn't granted. Defense-in-depth for sibling plugins whose Kotlin methods call framework APIs that themselves throw on missing permissions (e.g.TelecomManager.placeCall). Produces a clear library-specific error instead of the framework's opaqueSecurityException.requireAnyPermissionGranted(context, permissions)— mirror foranyOfframework contracts (e.g.CALL_PHONEorMANAGE_OWN_CALLS).requireAllPermissionsGranted(context, permissions)— mirror ofareAllPermissionsGranted;.deniedPermissionson the exception lists only the missing subset.requireRoleHeld(context, roleId)— throw-if-missing for default-app roles.PermissionDeniedException.deniedPermissions: List<String>— so callers can surface precise error UI.- These do not satisfy Android lint's
MissingPermissioncheck; that remains@RequiresPermission(anyOf = [...])'s job. The two compose.
Added — PermissionGrantStatus extension #
PermissionGrant.isSatisfied/isDenied/isUnsupported/isTerminal— promoted from private helpers insidePermissionResult. Used by the new gate helpers andPermissionResultso the "what counts as satisfied/denied/terminal" definition lives in one place.
Added — integration guide #
docs/INTEGRATION_GUIDE.mdcodifies the three-layer responsibility model (client app decides / sibling-plugin Dart declares / sibling-plugin native asserts), the Android lint cookbook (@RequiresPermission+PermissionGuards.require*), theBIND_*manifest pattern, and an iOS parity note. Includes a shippable-plugin checklist so new members of thesimple_*family land on the same pattern.
Toolchain #
- Android Java / Kotlin JVM target raised to 21 (was 17 for the plugin, 11 for the example). Aligns every sub-project so
javac, Kotlin, and AGP agree. compileSdk35 → 36 in the plugin; example app hardcodescompileSdk = 36so it doesn't drift withflutter.compileSdkVersion.- Example NDK 27.0.12077973 → 30.0.14904198.
- Gradle wrapper 8.11.1 → 8.13.
- Kotlin plugin 1.8.22 → 2.1.21 (1.8.x tops out at JVM target 19; Flutter tooling deprecates <2.1.0).
1.5.0 #
Fixed — correctness #
- Web geolocation double-settle:
BrowserPermissionsApi.requestGeolocationnow uses explicit single-shot discipline (asettledflag and tinysettle()helper). The browser contract is success XOR error, but a misbehaving or duplicated callback used to throw inside the JS-interop shim where the error was invisible. PermissionObserverleak whendispose()is forgotten: the lifecycle adapter now holds aWeakReferenceto the observer instead of pinning it, and aFinalizerregistered at construction runs a detach/close cleanup token if the observer is garbage-collected without explicit disposal. Debug builds log a one-line warning so the missingdispose()is visible during development.VersionedPermissionsilent fallthrough on Android:_resolve()now returnsPermission?and every call site (check/request/checkAll/requestAll/isSupported) treatsnullasPermissionGrant.notAvailable. Previously an out-of-range SDK would fall through to a registry miss and classify the permission asnotApplicable.
Added — tests & CI hygiene #
- Observer suite covers dispose ↔ in-flight refresh races: dispose-during-refresh, resume-after-dispose, and late-error-after-dispose interleavings.
- Analyzer elevated across root + every sub-package:
unawaited_futures,unused_import,unused_local_variable,dead_code,invalid_null_aware_operatorare errors. Addedprefer_null_aware_operators,cancel_subscriptions,close_sinks,test_types_in_equals,throw_in_finally,unnecessary_await_in_return; root addsuse_build_context_synchronouslyandavoid_slow_async_io.
Refactored #
- Browser permission state strings (
granted/denied/prompt) extracted tobrowser_permission_state.dartconstants. - Web
BrowserPermissionsApicatch (_)sites log todebugPrintin debug mode; production return values are unchanged. - Android registry field drops gratuitous
late— the initializer is cheap (const-built map) and every code path touches it.
Docs #
SimplePermissionsWebclass doc now documents the observer refresh cadence on web (tab-focus refreshes; noPermissionStatus.onchangewiring yet).
1.4.0 #
Added — native Kotlin helpers module #
PermissionGuardsobject (inpackages/simple_permissions_android) exposes a public Kotlin API that sibling plugins can call to check (read-only) whether a runtime permission is granted or whether the app holds a default-app role. Previously the only shared access-state API was Dart-side, forcing sibling plugins' Kotlin code to reach forContextCompat.checkSelfPermission(...)/RoleManager.isRoleHeld(...)directly.PermissionGuards.isPermissionGranted(context, permission): BooleanPermissionGuards.areAllPermissionsGranted(context, permissions): BooleanPermissionGuards.isRoleHeld(context, roleId): Boolean- Intentionally no request-side helpers — request flows surface UI and still belong behind the Dart API (
SimplePermissionsNative.instance.request(...)).
- Gradle wiring (verified working cross-repo): two-line setup in any consuming plugin — add
simple_permissions_nativeto its rootpubspec.yaml, thenimplementation(project(":simple_permissions_android"))in itsandroid/build.gradle[.kts]. Flutter's plugin-loader walks the pubspec graph at app-build time and synthesizes Gradle project entries for every federated plugin's Android module into the final app's build, so:simple_permissions_androidexists alongside the consuming plugin and the project ref resolves. Demonstrated in simple-sms PR #19. - Gotcha for local path-dep workflows: if the consuming plugin's example app uses
dependency_overridesforsimple_permissions_native, override each federated platform package (simple_permissions_android,simple_permissions_platform_interface, …) explicitly too — the plugin-loader registers Gradle paths per platform package, and a facade-only override leaves:simple_permissions_androidresolving to pub.dev, which manifests as "unresolved reference" on any new Kotlin API. See README for the full snippet. - Rule 2 ("access state goes through simple-permissions") is upheld at the Dart API boundary (request flows, observation, permission types) regardless.
PermissionGuardsis the cleanup path for plugins that want to eliminate inlineContextCompat.checkSelfPermission(...)and route native reads through the vocabulary owner too; plugins that can't or don't want to add the dep keep inline primitives fine.
1.3.0 #
- Added
PermissionObserver— reactive view over a set ofPermissions (includingAppRoles likeDefaultSmsApp/DefaultDialerApp) that re-queries on app resume and wheneverrefresh()is called. Exposed viaSimplePermissionsNative.instance.observe([...]).- Consumers drive reactive UI (disable writes until role held, show "grant to continue" banners) from
PermissionObserver.streamwithout writing their own lifecycle + poll loops. - Refresh triggers:
AppLifecycleState.resumed, explicitrefresh(), and the initial fetch at construction. Concurrent refresh calls are coalesced onto the in-flight future to keep result ordering predictable and avoid redundant platform work. - Platform failures are caught and routed onto the stream as errors rather than escaping as uncaught exceptions — important because the resume path fires refresh unawaited.
- Lifecycle wiring is pluggable via
PermissionObserverLifecycle; the defaultWidgetsBindingLifecyclehooksWidgetsBinding. Keeping this in the native (Flutter-aware) package — not the platform interface — so the platform interface stays Flutter-free. - Intentionally Dart-only — no native change needed. Matches platform reality (Android has no permission-change broadcast; re-query on resume is the accepted pattern).
- Consumers drive reactive UI (disable writes until role held, show "grant to continue" banners) from
1.2.0 #
- Added Apple example targets, Apple build validation, and Apple smoke-test coverage in CI.
- Refactored iOS and macOS native handlers into domain-specific Swift files with centralized registries.
- Split role-acquisition
Intentionpresets from the defaulttextingandcallingruntime-permission presets. - Consolidated Android Kotlin system-settings boilerplate into a data-driven dispatch pattern.
- Consolidated Dart
SystemSettingHandlerswitch duplication viaSystemSettingTypeenum methods. - Removed duplicate background-location foreground check from
RuntimePermissionHandler(orchestrator owns this logic). - Expanded README with platform setup guides,
PermissionGrantstate table,Intentionreference, and usage examples. - Improved example app with Intention-based flows, batch requests, and denied-state handling.
- Made
Intentionconst-constructible from user code (removedUnmodifiableListViewwrapping). - Added symmetric Kotlin test coverage for all system-setting request flows (overlay, install packages, schedule exact alarms).
- Added web platform support (
simple_permissions_web) with browser Permissions API for camera, microphone, geolocation, and notifications.
1.1.0 #
- Added location accuracy API:
checkLocationAccuracy()LocationAccuracyStatus(precise,reduced,none,notApplicable,notAvailable)
- Enforced Android background-location sequencing on API 30+:
BackgroundLocationrequests now require prior foreground location grant.- If requested first, the plugin returns
PermissionGrant.deniedand does not invoke runtime request.
- Added Android permission types:
BodySensorsBackgroundReadVoicemailAddVoicemailUwbRangingAcceptHandover
1.0.0 #
- Initial release of
simple_permissions_native. - Introduced federated plugin structure with:
simple_permissions_platform_interfacesimple_permissions_androidsimple_permissions_ios
- Added capability-based permission APIs for cross-platform checks and requests.
- Added detailed permission result modeling for richer status reporting.
- Included iOS privacy manifest support for App Store compliance.