Skip to main content

Runtime Permissions

API Summary (shared)

internal expect object AFPermissions {
@Throws(Throwable::class)
suspend fun isPermissionGranted(permission: Permission): Boolean

@Throws(Throwable::class)
suspend fun requestPermission(permission: Permission)
}

Supported logical permissions (enum Permission): CAMERA, COARSE_LOCATION, FINE_LOCATION, BACKGROUND_LOCATION, PHYSICAL_ACTIVITY, BLUETOOTH_LE, NOTIFICATIONS.


Android Usage

Lifecycle requirement
Register the permission host in your foreground ComponentActivity.onCreate().

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AFPermissions.registerLifecycle(this)
// ... UI setup
}
}

Check / Request a single permission

lifecycleScope.launch {
val hasCamera = AFPermissions.isPermissionGranted(Permission.CAMERA)
if (!hasCamera) {
AFPermissions.requestPermission(Permission.CAMERA)
}
}

Request multiple permissions (one sheet where possible)

lifecycleScope.launch {
val statuses = AFPermissions.requestAndGetStatuses(
listOf(
Permission.FINE_LOCATION,
Permission.PHYSICAL_ACTIVITY,
Permission.NOTIFICATIONS // Android 13+
)
)
// Handle statuses[Permission.X] (GRANTED, LIMITED, DENIED, RESTRICTED)
}

Background Location (2-step flow on Android 10+)

lifecycleScope.launch {
val status = AFPermissions.requestAndGetStatus(Permission.BACKGROUND_LOCATION)
// Internally ensures foreground location first, then requests background.
}

BLE / Proximity

lifecycleScope.launch {
val status = AFPermissions.requestAndGetStatus(Permission.BLUETOOTH_LE)
// Android 12L and below maps to Location; Android 12S+ asks for SCAN+CONNECT.
}

Settings shortcuts

AFPermissions.openAppSettings()
AFPermissions.openNotificationsSettings()

iOS Usage

iOS uses the actual implementation that bridges to native frameworks (CoreLocation, CoreBluetooth, CMMotion, AVFoundation, UserNotifications, HealthKit).
Swift Concurrency is supported: call from Task {} using try await.

Check / Request

import AFCore

Task {
do {
let granted = try await AFPermissions.shared.isPermissionGranted(permission: .camera)
if !granted {
try await AFPermissions.shared.requestPermission(permission: .camera)
}
} catch {
// Handle errors surfaced from the platform implementation
print("Permissions error: \(error)")
}
}

Background vs When-In-Use Location

Task {
// Request Always (the implementation internally sequences WhenInUse → Always when needed)
try await AFPermissions.shared.requestPermission(permission: .backgroundLocation)
}

Notifications

Task {
try await AFPermissions.shared.requestPermission(permission: .notifications)
// For push, also see: Device Utilities → Push Notifications
}

Open Settings

AFPermissions.shared.openAppSettings()

Permission Reference

Logical PermissionAndroid Runtime Gate (by API level)iOS Info.plist KeysNotes
CAMERAandroid.permission.CAMERANSCameraUsageDescriptionShow a user rationale before prompting.
COARSE_LOCATIONACCESS_COARSE_LOCATIONNSLocationWhenInUseUsageDescriptionCoarse ≈ approx location on Android; on iOS use When-In-Use.
FINE_LOCATIONACCESS_FINE_LOCATIONNSLocationWhenInUseUsageDescriptionIf only coarse is granted on Android, SDK reports LIMITED.
BACKGROUND_LOCATIONACCESS_BACKGROUND_LOCATION (API 29+)NSLocationAlwaysAndWhenInUseUsageDescription (also WhenInUse)Android is 2-step (FG → BG). iOS maps When-In-Use → Always where applicable.
PHYSICAL_ACTIVITYACTIVITY_RECOGNITION (API 29+)NSMotionUsageDescriptionPre-29 Android has no runtime gate (treated as granted).
BLUETOOTH_LEBLUETOOTH_SCAN, BLUETOOTH_CONNECT (API 31+). On 29–30: Location.NSBluetoothAlwaysUsageDescription (iOS 13+)SDK normalizes to GRANTED / LIMITED / DENIED / RESTRICTED.
NOTIFICATIONSPOST_NOTIFICATIONS (API 33+)(none required)iOS authorization via UNUserNotificationCenter; see Push Notifications for APNs/FCM wiring.

Common Patterns & Tips

  • Just-in-time prompts: Ask only when a feature is used.
  • Rationales: Show an in-app screen explaining “why” before system dialogs.
  • Denied vs Restricted: On Android, “Don’t ask again” is mapped to RESTRICTED; on iOS some frameworks return RESTRICTED for parental controls/MDM.
  • Testing flows: Simulators may not exercise all paths (e.g., Bluetooth, Motion). Use real hardware.
  • Push notifications: Request authorization here, then complete FCM/APNs setup in Push Notifications.

Troubleshooting

  • Background geofencing not firing (iOS) → Ensure Always Location and background modes are enabled; verify region limits.
  • BLE scan returns no results → Check that the correct permission gate is used for the OS version.
  • Android: host not registered → Call AFPermissions.registerLifecycle(activity) in the foreground Activity.onCreate().
  • iOS: no prompt shows → Missing Info.plist key; see the table above.