Coverage Summary for Class: PermissionFlow (dev.shreyaspatil.permissionFlow)

Class Method, % Branch, % Line, % Instruction, %
PermissionFlow$Companion 100% (4/4) 100% (2/2) 100% (6/6) 100% (32/32)
Total 100% (4/4) 100% (2/2) 100% (6/6) 100% (32/32)


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 }