Coverage Summary for Class: PermissionFlow (dev.shreyaspatil.permissionFlow)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
PermissionFlow$Companion |
100%
(4/4)
|
100%
(2/2)
|
100%
(4/4)
|
100%
(35/35)
|
Total |
100%
(4/4)
|
100%
(2/2)
|
100%
(4/4)
|
100%
(35/35)
|
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 }