Changelog
0.8.2 — 2026-04-15
Documentation-only patch. No runtime changes.
Documentation
- Added recipe: Imperative camera access — button-triggered permission flows inside a mid-form context (KYC, ID capture, profile photos) that must survive denial without unmounting form state. Uses
usePermissionHandlerdirectly withrenderPrePrompt/renderBlockedPromptrender props and theuifield as a sibling of the form. - Added recipe: Location accuracy UI — render precise vs approximate map and ETA from
result.metadata.locationAccuracyon iOS 14+ (Expo engine), paired withrecheckOnForegroundso the UI recovers when the user toggles Precise Location in Settings.
0.8.1 — 2026-04-15
Small follow-up patch. Surfaces the Expo location-accuracy metadata on the hook result that v0.8.0 only captured internally, plus two documentation additions from post-release feedback.
New features
PermissionHandlerResult.metadata— engine-specific metadata snapshot, populated when the resolved engine implements the optional newPermissionEngine.getMetadata()method. Currently only the Expo engine populates a field:metadata.locationAccuracyis"full"or"reduced"on iOS 14+ after a location permission call via Expo SDK 55+. The field shape is intentionally tight — new fields are added only when a concrete engine + use case justifies them.
Docs
- New recipe: speech-recognition — iOS needs both
MICROPHONEandSPEECH_RECOGNITION; Android needs onlyRECORD_AUDIO. Platform-aware sequential flow with stableidkeys. - README: new App Tracking Transparency timing gotcha in the iOS Platform gotchas section. ATT can only be requested while the app is in the
.activestate; calling it from a cold-start mount effect silently returnsdenied. Rule of thumb included.
Non-breaking
All changes are additive. PermissionEngine.getMetadata is optional — engines that don't implement it continue to work (the hook returns an empty metadata object). Existing code destructuring PermissionHandlerResult is unaffected.
0.8.0 — 2026-04-15
The Permission Recovery & State Reliability release. Every v0.7.0-era recovery path is now first-class: openSettings deep-links to the per-permission sub-page on iOS, state updates reactively when the user toggles permissions outside your flow, imperative flows get render-prop ergonomics, and there's a new refresh() escape hatch for corrupted grants.
All changes are additive. Existing v0.7.0 code continues to work.
New features
refresh()onPermissionHandlerResult— force a freshengine.request()bypassingcheck(). Use this when the native status reportsgrantedbut the permission is functionally broken (e.g. iOS 18 camera/photo corrupted-grant bug). From terminal states (granted,limited,denied,blocked,unavailable) transitions torequestingand re-runs the native dialog. From non-terminal states it's a no-op. Backed by a newREFRESHevent in the state machine.engine.openSettings(permission?)— optional permission parameter enables best-effort iOS Settings deep-linking via the unofficialApp-Prefs:URL scheme. Supports camera, microphone, photos, location, contacts, calendars, reminders, motion, and Bluetooth, with substring matching that accepts RNP constants, Expo keys, and plain strings. Falls back to generic Settings when the deep-link fails.usePermissionHandleranduseMultiplePermissionspass their configured permission automatically — no call-site changes required.renderPrePrompt/renderBlockedPromptonPermissionHandlerConfig— imperative hook-driven flows (KYC camera, inline composers, button-driven flows) can now use render-prop ergonomics without wrapping inPermissionGate. The hook result exposes a computeduinode that renders the matching function for the current state, ornullotherwise. Bare-hook users can render{handler.ui}inline in their layout.createExpoEnginenow reads theios.accuracyfield fromexpo-locationresponses (Expo SDK 55+) and exposes it via a newgetLastLocationAccuracy()method on theExpoEnginetype. Not yet surfaced onPermissionHandlerResult— that's a v0.9.0 design topic.
Fixes
- State machine:
REQUEST_RESULT:unavailableandRECHECK_RESULT:unavailablenow transition to the terminalunavailablestate. Previously the default case left the flow stuck inrequestingorrecheckingAfterSettingsindefinitely — a latent freeze that no test covered.
Docs
- New recipe: recheck-on-foreground — full semantics, blip handling, and guidance on when to enable
recheckOnForeground: true. - New recipe: stale-permission-state — workaround for the Expo cold-start stale-
undeterminedbug (expo/expo#42084). - Updated recipe: background-location now documents the iOS "Always Allow" upgrade path via
openSettings("location")deep-link. - New guide: iOS Privacy Manifest —
PrivacyInfo.xcprivacyboilerplate template and fullInfo.plistusage-description reference. - README: new "What this library doesn't cover" section disclaiming HealthKit, OAuth scopes, IAP entitlements, push provider tokens, etc.
- Types: clarified
"limited"status scope and the iOS 18 limited-contacts caveat.
Non-breaking
All changes are additive. PermissionHandlerResult.refresh and .ui are new fields; existing code that destructures the result continues to work. PermissionEngine.openSettings gained an optional parameter; engines that don't implement it still work. createExpoEngine now returns the ExpoEngine type (extends PermissionEngine) — existing code typed against PermissionEngine is unaffected.
0.7.0
See the git history for the v0.7.0 changes. Highlights:
- Android post-dialog status normalization (opt-in via
createRNPEngine({ normalizeAndroid })) requestFullAccess()wired through the hook andrenderLimitedonPermissionGatePermissions.BUNDLESfor BLE, LOCATION_BACKGROUND, and CALENDARS_WRITE_ONLYskipPrePrompt: true | "android"for one-tap composer flowsuseMultiplePermissions.resume()to continue a stopped sequential flow- Optional
prePrompt/blockedPromptconfig - Android 16
request()hang auto-recovery via 5 s defaultrequestTimeout - Photo library
unavailable → blockedengine normalization - Wired
recheckOnForegroundconfig option - Platform-aware
LOCATION_BACKGROUNDbundle (iOS single-authorization, Android two entries) createTestingEnginesymmetric defaults +autoGrantUnsetopt-inallGrantedfalse on empty permission arrays