moko-permissions

repository·master·Indexed 20 days ago

https://github.com/icerockdev/moko-permissions

A Kotlin Multiplatform library providing a unified way to handle runtime permissions on Android and iOS. It allows developers to request permissions from common code using PermissionsController, with support for modular permissions including Camera, Location, Bluetooth, and more. The library includes specific exception handling for permission denials (DeniedException, DeniedAlwaysException) and request cancellations (RequestCanceledException), and provides integration for Compose Multiplatform.

Tokens
3K
Snippets
9
Records
10
Agent score
69%

What's inside moko-permissions

  1. Install moko-permissions

    master

    To use moko-permissions in your Kotlin Multiplatform project, add mavenCentral() to your root build.gradle and include the core library and any specific permission modules you need in your project's build.gradle dependencies.

    Note that permissions are modularized. For example, if you need Camera or Location permissions, you must include permissions-camera or permissions-location respectively.

    // root build.gradle
    allprojects {
        repositories {
          mavenCentral()
        }
    }
    
    // project build.gradle
    dependencies {
        commonMainApi("dev.icerock.moko:permissions:0.20.1")
      
        // specific permissions support
        commonMainImplementation("dev.icerock.moko:permissions-bluetooth:0.20.1")
        commonMainImplementation("dev.icerock.moko:permissions-camera:0.20.1")
        commonMainImplementation("dev.icerock.moko:permissions-contacts:0.20.1")
        commonMainImplementation("dev.icerock.moko:permissions-gallery:0.20.1")
        commonMainImplementation("dev.icerock.moko:permissions-location:0.20.1")
        commonMainImplementation("dev.icerock.moko:permissions-microphone:0.20.1")
        commonMainImplementation("dev.icerock.moko:permissions-motion:0.20.1")
        commonMainImplementation("dev.icerock.moko:permissions-notifications:0.20.1")
        commonMainImplementation("dev.icerock.moko:permissions-storage:0.20.1")
        
        // compose multiplatform
        commonMainApi("dev.icerock.moko:permissions-compose:0.20.1")
        
        commonTestImplementation("dev.icerock.moko:permissions-test:0.20.1")
    }
  2. Bind PermissionsController to Android Lifecycle

    master

    On Android, the PermissionsController must be bound to the Activity lifecycle to ensure permission requests are handled correctly and safely. Call permissionsController.bind(activity) within your Activity's onCreate method.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
            
        val viewModel = getViewModel {
            // Pass the platform implementation of the permission controller to a common code.
            ViewModel(PermissionsController())
        }
        
        // Binds the permissions controller to the activity lifecycle.
        viewModel.permissionsController.bind(activity)
    }
  3. Use moko-permissions with Compose Multiplatform

    master

    For Compose Multiplatform, you can use PermissionsControllerFactory to create a controller.

    If you are using standard Compose, use BindEffect(controller) to bind the controller to the LocalLifecycleOwner.

    If you are using moko-mvvm, ensure you bind the controller in your Composable to handle configuration changes on Android correctly.

    @Composable
    fun Sample() {
        val factory: PermissionsControllerFactory = rememberPermissionsControllerFactory()
        val controller: PermissionsController = remember(factory) { factory.createPermissionsController() }
        val coroutineScope: CoroutineScope = rememberCoroutineScope()
        
        Button(
            onClick = {
                coroutineScope.launch {
                    controller.providePermission(Permission.REMOTE_NOTIFICATION)
                }
            }
        ) {
            Text(text = "give permissions")
        }
    }
  4. Request permissions using PermissionsController

    master

    In your common code, use the PermissionsController to request permissions. The providePermission(Permission) method is a suspending function that attempts to grant the specified permission.

    You must handle two specific exceptions to manage user denials:

    • DeniedAlwaysException: The user has permanently denied the permission (e.g., selected 'Don't ask again').
    • DeniedException: The user denied the permission for the current request.
    class ViewModel(val permissionsController: PermissionsController): ViewModel() {
        fun onPhotoPressed() {
            viewModelScope.launch {
                try {
                    permissionsController.providePermission(Permission.GALLERY)
                    // Permission has been granted successfully.
                } catch(deniedAlways: DeniedAlwaysException) {
                    // Permission is always denied.
                } catch(denied: DeniedException) {
                    // Permission was denied.
                }
            }
        }
    }
  5. Reference: Supported Permissions and Modules

    master

    The following modules and their corresponding Permission constants are supported:

    • Bluetooth (dev.icerock.moko:permissions-bluetooth):
      • Permission.BLUETOOTH_LE
      • Permission.BLUETOOTH_SCAN
      • Permission.BLUETOOTH_CONNECT
      • Permission.BLUETOOTH_ADVERTISE
    • Camera (dev.icerock.moko:permissions-camera):
      • Permission.CAMERA
    • Contacts (dev.icerock.moko:permissions-contacts):
      • Permission.CONTACTS
    • Gallery (dev.icerock.moko:permissions-gallery):
      • Permission.GALLERY
    • Location (dev.icerock.moko:permissions-location):
      • Permission.LOCATION (Fine)
      • Permission.COARSE_LOCATION (Coarse)
      • Permission.BACKGROUND_LOCATION (Background)
    • Microphone (dev.icerock.moko:permissions-microphone):
      • Permission.RECORD_AUDIO
    • Motion (dev.icerock.moko:permissions-motion):
      • Permission.MOTION
    • Notifications (dev.icerock.moko:permissions-notifications):
      • Permission.REMOTE_NOTIFICATION
    • Storage (dev.icerock.moko:permissions-storage):
      • Permission.STORAGE (Read)
      • Permission.WRITE_STORAGE (Write)
  6. Open system app settings with openAppSettings()

    master

    The openAppSettings() method triggers the iOS system to open the settings application directly to the settings page for your app. This is useful when a user has denied a permission and you need to guide them to manually enable it in the system settings.

    val controller = PermissionsController()
    controller.openAppSettings()
  7. Use PermissionsController on iOS

    master

    On iOS, the PermissionsController implements the PermissionsControllerProtocol to manage runtime permissions. It provides methods to request permissions, check their current state, and open the system settings app.

    Note that this implementation relies on the delegate property of the Permission object to perform the actual platform-specific logic.

    val controller = PermissionsController()
    
    // Request a permission
    controller.providePermission(permission)
    
    // Check if a permission is granted
    val isGranted = controller.isPermissionGranted(permission)
    
    // Get the full permission state
    val state = controller.getPermissionState(permission)
    
    // Open system settings
    controller.openAppSettings()
  8. Handle permission denial with DeniedException

    master

    When a user denies a permission request, the library throws a DeniedException. You can catch this exception to identify which specific Permission was denied and react accordingly (e.g., by showing a rationale UI).

    DeniedException is the base class for all permission denial errors in the library.

    try {
        permissionsController.requestPermission(Permission.CAMERA)
    } catch (e: DeniedException) {
        val deniedPermission = e.permission
        // Handle the denial of the specific permission
    }
  9. Handle permanent permission denial with DeniedAlwaysException

    master

    If a user has selected 'Don't ask again' (or the platform equivalent) and the permission is permanently denied, the library throws a DeniedAlwaysException. This is a subclass of DeniedException.

    Catching this specific exception allows you to distinguish between a simple denial (where you might ask again) and a permanent denial (where you must direct the user to the system settings).

    try {
        permissionsController.requestPermission(Permission.CAMERA)
    } catch (e: DeniedAlwaysException) {
        // The user has permanently denied this permission.
        // You should likely direct them to the app settings.
    } catch (e: DeniedException) {
        // The user just denied it this time.
    }
  10. Handle canceled permission requests with RequestCanceledException

    master

    When a user cancels a permission request (e.g., by dismissing the system dialog), the library throws a RequestCanceledException. This exception contains a permission property that identifies which specific permission was being requested at the time of cancellation. You should catch this exception to prevent your application from crashing and to handle the user's decision to deny the request gracefully.

    try {
        permissionsController.requestPermission(Permission.CAMERA)
    } catch (e: RequestCanceledException) {
        // The user canceled the request for the specific permission
        println("Permission ${e.permission} was canceled by the user")
    }