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 }