Coverage Summary for Class: PermissionFlow (dev.shreyaspatil.permissionFlow)
| Class | Method, % | Branch, % | Line, % | Instruction, % | 
|---|---|---|---|---|
| PermissionFlow | 0% (0/2) | 0% (0/2) | ||
| PermissionFlow$Companion | 80% (4/5) | 100% (2/2) | 83.3% (5/6) | 100% (23/23) | 
| Total | 57.1% (4/7) | 100% (2/2) | 62.5% (5/8) | 100% (23/23) | 
1 /** 2 * Copyright 2022 Shreyas Patil 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package dev.shreyaspatil.permissionFlow 17 18 import android.content.Context 19 import dev.shreyaspatil.permissionFlow.PermissionFlow.Companion.getInstance 20 import dev.shreyaspatil.permissionFlow.PermissionFlow.Companion.init 21 import dev.shreyaspatil.permissionFlow.impl.PermissionFlowImpl 22 import kotlinx.coroutines.CoroutineDispatcher 23 import kotlinx.coroutines.DelicateCoroutinesApi 24 import kotlinx.coroutines.flow.StateFlow 25 import kotlinx.coroutines.newFixedThreadPoolContext 26 27 /** 28 * A utility class which provides a functionality for observing state of a permission (whether it's 29 * granted or not) with reactive coroutine stream i.e. [StateFlow]. 30 * 31 * This takes care of listening to permission state change from any screen throughout the 32 * application so that you can listen to permission in any layer of architecture within app. 33 * 34 * To retrieve the instance, use [getInstance] method but make sure to initialize it with [init] 35 * method before retrieving instance. Otherwise, it'll throw [IllegalStateException] 36 * 37 * Example usage: 38 * 39 * **1. Initialization** 40 * 41 * ``` 42 * class MyApplication: Application() { 43 * override fun onCreate() { 44 * super.onCreate() 45 * PermissionFlow.init(this) 46 * } 47 * } 48 * ``` 49 * 50 * **2. Observing permission** 51 * 52 * ``` 53 * val permissionFlow = PermissionFlow.getInstance() 54 * 55 * fun observeContactsPermission() { 56 * coroutineScope.launch { 57 * permissionFlow.getPermissionState(android.Manifest.permission.READ_CONTACTS) 58 * .collect { state -> 59 * if (state.isGranted) { 60 * // Do something 61 * } else { 62 * if (state.isRationaleRequired) { 63 * // Do something 64 * } 65 * } 66 * } 67 * } 68 * } 69 * ``` 70 * 71 * **3. Launching permission** 72 * 73 * ``` 74 * class MyActivity: AppCompatActivity() { 75 * private val permissionLauncher = registerForPermissionFlowRequestsResult() 76 * 77 * fun askContactPermission() { 78 * permissionLauncher.launch(android.Manifest.permission.READ_CONTACTS) 79 * } 80 * } 81 * ``` 82 * 83 * This utility tries to listen to permission state change which may not happen within a application 84 * (_e.g. user trying to allow permission from app settings_), but doesn't guarantee that you'll 85 * always get a updated state at the accurate instant. 86 */ 87 interface PermissionFlow { 88 /** 89 * Returns [StateFlow] for a given [permission] 90 * 91 * @param permission Unique permission identity (for e.g. 92 * [android.Manifest.permission.READ_CONTACTS]) 93 * 94 * Example: 95 * ``` 96 * permissionFlow.getPermissionState(android.Manifest.permission.READ_CONTACTS) 97 * .collect { state -> 98 * if (state.isGranted) { 99 * // Do something 100 * } else { 101 * if (state.isRationaleRequired) { 102 * // Do something 103 * } 104 * } 105 * } 106 * ``` 107 */ 108 fun getPermissionState(permission: String): StateFlow<PermissionState> 109 110 /** 111 * Returns [StateFlow] of a combining state for [permissions] 112 * 113 * @param permissions List of permissions (for e.g. [android.Manifest.permission.READ_CONTACTS], 114 * [android.Manifest.permission.READ_SMS]) 115 * 116 * Example: 117 * ``` 118 * permissionFlow.getMultiplePermissionState( 119 * android.Manifest.permission.READ_CONTACTS, 120 * android.Manifest.permission.READ_SMS 121 * ).collect { state -> 122 * // All permission states 123 * val allPermissions = state.permissions 124 * 125 * // Check whether all permissions are granted 126 * val allGranted = state.allGranted 127 * 128 * // List of granted permissions 129 * val grantedPermissions = state.grantedPermissions 130 * 131 * // List of denied permissions 132 * val deniedPermissions = state.deniedPermissions 133 * } 134 * ``` 135 */ 136 fun getMultiplePermissionState(vararg permissions: String): StateFlow<MultiplePermissionState> 137 138 /** 139 * This helps to check if specified [permissions] are changed and it verifies it and updates the 140 * state of permissions which are being observed via [getMultiplePermissionState] method. 141 * 142 * This can be useful when you are not using result launcher which is provided with this library 143 * and manually handling permission request and want to update the state of permission in this 144 * library so that flows which are being observed should get an updated state. 145 * 146 * If [stopListening] is called earlier and hasn't started listening again, notifying permission 147 * doesn't work. Its new state is automatically calculated after starting listening to states 148 * again by calling [startListening] method. 149 * 150 * Example usage: 151 * 152 * In this example, we are not using result launcher provided by this library. So we are 153 * manually notifying library about state change of a permission. 154 * 155 * ``` 156 * class MyActivity: AppCompatActivity() { 157 * private val permissionFlow = PermissionFlow.getInstance() 158 * private val permissionLauncher = registerForActivityResult(RequestPermission()) { isGranted -> 159 * permissionFlow.notifyPermissionsChanged(android.Manifest.permission.READ_CONTACTS) 160 * } 161 * } 162 * ``` 163 * 164 * @param permissions List of permissions 165 */ 166 fun notifyPermissionsChanged(vararg permissions: String) 167 168 /** 169 * Starts listening the changes of state of permissions. 170 * 171 * Ideally it automatically starts listening eagerly when application is started and created via 172 * [dev.shreyaspatil.permissionFlow.initializer.PermissionFlowInitializer]. If initializer is 173 * disabled, then starts listening lazily when [getPermissionState] [getPermissionEvent] or 174 * [getMultiplePermissionState] method is used for the first time. But this can be used to start 175 * to listen again after stopping listening with [stopListening]. 176 */ 177 fun startListening() 178 179 /** 180 * Stops listening to the state changes of permissions throughout the application. This means 181 * the state of permission retrieved with [getMultiplePermissionState] method will not be 182 * updated after stopping listening. To start to listen again, use [startListening] method. 183 */ 184 fun stopListening() 185 186 /** 187 * Companion of [PermissionFlow] to provide initialization of [PermissionFlow] as well as 188 * getting instance. 189 */ 190 companion object { 191 @OptIn(DelicateCoroutinesApi::class) 192 private val DEFAULT_DISPATCHER = newFixedThreadPoolContext(2, "PermissionFlow") 193 194 /** 195 * Initializes this [PermissionFlow] instance with specified arguments. 196 * 197 * @param context The Android's [Context]. Application context is recommended. 198 * @param dispatcher Coroutine dispatcher to be used in the [PermissionFlow]. By default, it 199 * utilizes dispatcher having fixed two number of threads. 200 */ 201 @JvmStatic 202 @JvmOverloads 203 fun init(context: Context, dispatcher: CoroutineDispatcher = DEFAULT_DISPATCHER) { 204 PermissionFlowImpl.init(context, dispatcher) 205 } 206 207 /** 208 * Returns an instance with default implementation of [PermissionFlow]. 209 * 210 * @return Instance of [PermissionFlow]. 211 * @throws IllegalStateException If method [init] is not called before using this method. 212 */ 213 @JvmStatic 214 fun getInstance(): PermissionFlow = 215 PermissionFlowImpl.instance 216 ?: error( 217 "Failed to create instance of PermissionFlow. Did you forget to call `PermissionFlow.init(context)`?") 218 } 219 }