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

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


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.asCoroutineDispatcher 24 import kotlinx.coroutines.flow.StateFlow 25 import java.util.concurrent.Executors 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  * } 62  * } 63  * } 64  * } 65  *``` 66  * 67  * **3. Launching permission** 68  * 69  * ``` 70  * class MyActivity: AppCompatActivity() { 71  * private val permissionLauncher = registerForPermissionFlowRequestsResult() 72  * 73  * fun askContactPermission() { 74  * permissionLauncher.launch(android.Manifest.permission.READ_CONTACTS) 75  * } 76  * } 77  * ``` 78  * 79  * This utility tries to listen to permission state change which may not happen within a application 80  * (_e.g. user trying to allow permission from app settings_), but doesn't guarantee that you'll 81  * always get a updated state at the accurate instant. 82  */ 83 interface PermissionFlow { 84  /** 85  * Returns [StateFlow] for a given [permission] 86  * 87  * @param permission Unique permission identity 88  * (for e.g. [android.Manifest.permission.READ_CONTACTS]) 89  * 90  * Example: 91  * 92  * ``` 93  * permissionFlow.getPermissionState(android.Manifest.permission.READ_CONTACTS) 94  * .collect { state -> 95  * if (state.isGranted) { 96  * // Do something 97  * } 98  * } 99  * ``` 100  */ 101  fun getPermissionState(permission: String): StateFlow<PermissionState> 102  103  /** 104  * Returns [StateFlow] of a combining state for [permissions] 105  * 106  * @param permissions List of permissions 107  * (for e.g. [android.Manifest.permission.READ_CONTACTS], [android.Manifest.permission.READ_SMS]) 108  * 109  * Example: 110  * 111  * ``` 112  * permissionFlow.getMultiplePermissionState( 113  * android.Manifest.permission.READ_CONTACTS, 114  * android.Manifest.permission.READ_SMS 115  * ).collect { state -> 116  * // All permission states 117  * val allPermissions = state.permissions 118  * 119  * // Check whether all permissions are granted 120  * val allGranted = state.allGranted 121  * 122  * // List of granted permissions 123  * val grantedPermissions = state.grantedPermissions 124  * 125  * // List of denied permissions 126  * val deniedPermissions = state.deniedPermissions 127  * } 128  * ``` 129  */ 130  fun getMultiplePermissionState(vararg permissions: String): StateFlow<MultiplePermissionState> 131  132  /** 133  * This helps to check if specified [permissions] are changed and it verifies it and updates 134  * the state of permissions which are being observed via [getMultiplePermissionState] method. 135  * 136  * This can be useful when you are not using result launcher which is provided with this library 137  * and manually handling permission request and want to update the state of permission in this 138  * library so that flows which are being observed should get an updated state. 139  * 140  * If [stopListening] is called earlier and hasn't started listening again, notifying 141  * permission doesn't work. Its new state is automatically calculated after starting 142  * listening to states again by calling [startListening] method. 143  * 144  * Example usage: 145  * 146  * In this example, we are not using result launcher provided by this library. So we are 147  * manually notifying library about state change of a permission. 148  * 149  * ``` 150  * class MyActivity: AppCompatActivity() { 151  * private val permissionFlow = PermissionFlow.getInstance() 152  * private val permissionLauncher = registerForActivityResult(RequestPermission()) { isGranted -> 153  * permissionFlow.notifyPermissionsChanged(android.Manifest.permission.READ_CONTACTS) 154  * } 155  * } 156  * ``` 157  * 158  * @param permissions List of permissions 159  */ 160  fun notifyPermissionsChanged(vararg permissions: String) 161  162  /** 163  * Starts listening the changes of state of permissions. 164  * 165  * Ideally it automatically starts listening lazily when [getMultiplePermissionState] method is used for the first 166  * time. But this can be used to start to listen again after stopping listening 167  * with [stopListening]. 168  */ 169  fun startListening() 170  171  /** 172  * Stops listening to the state changes of permissions throughout the application. 173  * This means the state of permission retrieved with [getMultiplePermissionState] method will not be updated after 174  * stopping listening. To start to listen again, use [startListening] method. 175  */ 176  fun stopListening() 177  178  /** 179  * Companion of [PermissionFlow] to provide initialization of [PermissionFlow] as well as 180  * getting instance. 181  */ 182  companion object { 183  private val DEFAULT_DISPATCHER = Executors.newFixedThreadPool(2).asCoroutineDispatcher() 184  185  /** 186  * Initializes this [PermissionFlow] instance with specified arguments. 187  * 188  * @param context The Android's [Context]. Application context is recommended. 189  * @param dispatcher Coroutine dispatcher to be used in the [PermissionFlow]. By default, 190  * it utilizes dispatcher having fixed two number of threads. 191  */ 192  @JvmStatic 193  @JvmOverloads 194  fun init(context: Context, dispatcher: CoroutineDispatcher = DEFAULT_DISPATCHER) { 195  PermissionFlowImpl.init(context, dispatcher) 196  } 197  198  /** 199  * Returns an instance with default implementation of [PermissionFlow]. 200  * 201  * @throws IllegalStateException If method [init] is not called before using this method. 202  * 203  * @return Instance of [PermissionFlow]. 204  */ 205  @JvmStatic 206  fun getInstance(): PermissionFlow = PermissionFlowImpl.instance 207  ?: error("Failed to create instance of PermissionFlow. Did you forget to call `PermissionFlow.init(context)`?") 208  } 209 }