Blog Infos
Author
Published
Topics
,
Published

The introduction of Jetpack Compose changes the way we build UI in Android. It simplifies and accelerates UI development in Android. One component of Android UI development that also gets affected by this change is how we handle permissions in Android. The Compose Accompanist Permission library provides easy-to-use Compose API for handling permission in a Jetpack Compose Architecture. However, it doesn’t take away the boilerplates of writing repeated codes to handle permissions.

In this article, I will show you how to create a reusable permissions handler for Jetpack Compose UI. We will be using the Accompanist Permission Library to create this permissions handler class.

implementation("com.google.accompanist:accompanist-permissions:0.23.1")

Let’s start by creating a “PermissionsHandler” class that will handle all permission-related Events by invoking a State update which in turn triggers our custom permission composable to perform the requested Permission Action. Here is how the PermissionsHandler looks like

class PermissionsHandler {
private val _state = MutableStateFlow(State())
val state: StateFlow<State> = _state
fun onEvent(event: Event) {
when (event) {
Event.PermissionDenied -> onPermissionDenied()
Event.PermissionDismissTapped -> onPermissionDismissTapped()
Event.PermissionNeverAskAgain -> onPermissionNeverShowAgain()
Event.PermissionRationaleOkTapped -> onPermissionRationaleOkTapped()
Event.PermissionRequired -> onPermissionRequired()
Event.PermissionSettingsTapped -> onPermissionSettingsTapped()
Event.PermissionsGranted -> onPermissionGranted()
is Event.PermissionsStateUpdated -> onPermissionsStateUpdated(event.permissionsState)
}
}
data class State(
val multiplePermissionsState: MultiplePermissionsState? = null,
val permissionAction: Action = Action.NO_ACTION
)
sealed class Event {
object PermissionDenied : Event()
object PermissionsGranted : Event()
object PermissionSettingsTapped : Event()
object PermissionNeverAskAgain : Event()
object PermissionDismissTapped : Event()
object PermissionRationaleOkTapped : Event()
object PermissionRequired : Event()
data class PermissionsStateUpdated(val permissionsState: MultiplePermissionsState) :
Event()
}
enum class Action {
REQUEST_PERMISSION, SHOW_RATIONALE, SHOW_NEVER_ASK_AGAIN, NO_ACTION
}
}

We have just 2 variables in the Permission Handler “State, the “multiplePermissionsState provides information about the state of the permissions required. This property would tell us if a required permission is denied or granted, or if a permission rationale screen needs to be displayed.

We get the “multiplePermissionsState” from the Accompanist Permission API “rememberMultiplePermissionsState”, then passed to the “PermissionHandler.State” via the “PermissionStateUpdated” event. Very soon we will see how the event “PermissionStateUpdated” and other events are being dispatched. For now, let’s implement the methods to handle those Events.

class PermissionsHandler {
private fun onPermissionsStateUpdated(permissionState: MultiplePermissionsState) {
_state.update { it.copy(multiplePermissionsState = permissionState) }
}
private fun onPermissionGranted() {
_state.update { it.copy(permissionAction = Action.NO_ACTION) }
}
private fun onPermissionDenied() {
_state.update { it.copy(permissionAction = Action.NO_ACTION) }
}
private fun onPermissionNeverShowAgain() {
_state.update {
it.copy(permissionAction = Action.SHOW_NEVER_ASK_AGAIN)
}
}
private fun onPermissionRequired() {
_state.value.multiplePermissionsState?.let {
val permissionAction =
if (!it.allPermissionsGranted && !it.shouldShowRationale && !it.permissionRequested) {
Action.REQUEST_PERMISSION
} else if (!it.allPermissionsGranted && it.shouldShowRationale) {
Action.SHOW_RATIONALE
} else {
Action.SHOW_NEVER_ASK_AGAIN
}
_state.update { it.copy(permissionAction = permissionAction) }
}
}
private fun onPermissionRationaleOkTapped() {
_state.update { it.copy(permissionAction = Action.REQUEST_PERMISSION) }
}
private fun onPermissionDismissTapped() {
_state.update { it.copy(permissionAction = Action.NO_ACTION) }
}
private fun onPermissionSettingsTapped() {
_state.update { it.copy(permissionAction = Action.NO_ACTION) }
}
}

For the “PermissionRequired” event, we check the “multiplePermissionsState” to decide the appropriate permission action to use.

Now let’s create a composable that will handle the “permissionAction

@Composable
fun HandlePermissionAction(
action: PermissionsHandler.Action,
permissionStates: MultiplePermissionsState?,
@StringRes rationaleText: Int,
@StringRes neverAskAgainText: Int,
onOkTapped: () -> Unit,
onSettingsTapped: () -> Unit,
) {
val context = LocalContext.current
when (action) {
PermissionsHandler.Action.REQUEST_PERMISSION -> {
LaunchedEffect(true) {
permissionStates?.launchMultiplePermissionRequest()
}
}
PermissionsHandler.Action.SHOW_RATIONALE -> {
PermissionRationaleDialog(
message = stringResource(rationaleText),
onOkTapped = onOkTapped
)
}
PermissionsHandler.Action.SHOW_NEVER_ASK_AGAIN -> {
ShowGotoSettingsDialog(
title = stringResource(R.string.allow_permission),
message = stringResource(neverAskAgainText),
onSettingsTapped = {
onSettingsTapped()
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:" + context.packageName)
context.startActivity(this)
}
},
)
}
PermissionsHandler.Action.NO_ACTION -> Unit
}
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

PermissionRationaleDialog” and “ShowGotoSettingsDialog” are custom composables I created. Those can be replaced with your preferred composables. When the permission action is “REQUEST_PERMISSION” we invoke the “launchMultiplePermissionRequest” method from the accompanist library.

The final step for us is to create a “HandlePermissionsRequest” composable that can be slotted into any Compose UI. The composable takes in a PermissionHandler as a parameter along with a list of required permissions. The 2 arguments can be passed from the parent composable.

@Composable
fun HandlePermissionsRequest(permissions: List<String>, permissionsHandler: PermissionsHandler) {
val state by permissionHandler.state.collectAsState()
val permissionsState = rememberMultiplePermissionsState(permissions)
LaunchedEffect(permissionsState) {
permissionHandler.onEvent(PermissionHandler.Event.PermissionsStateUpdated(permissionsState))
when {
permissionsState.allPermissionsGranted -> {
permissionsHandler.onEvent(PermissionsHandler.Event.PermissionsGranted)
}
permissionsState.permissionRequested && !permissionsState.shouldShowRationale -> {
permissionsHandler.onEvent(PermissionsHandler.Event.PermissionNeverAskAgain)
}
else -> {
permissionsHandler.onEvent(PermissionsHandler.Event.PermissionDenied)
}
}
}
HandlePermissionAction(
action = state.permissionAction,
permissionStates = state.multiplePermissionsState,
rationaleText = R.string.permission_rationale,
neverAskAgainText = R.string.permission_rationale,
onOkTapped = { permissionsHandler.onEvent(PermissionsHandler.Event.PermissionRationaleOkTapped) },
onSettingsTapped = { permissionsHandler.onEvent(PermissionsHandler.Event.PermissionSettingsTapped) },
)
}

Reviewing the code above, we see how “Events” are dispatched to the “PermissionHandler”, including the “PermissionsStateUpdated” event. A change to “permissionsState” will cause the LaunchedEffect compose API to be executed.

At this point, we have created a reusable permission-handling class for any Compose UI. To use this permissions handler, we can either create the PermissionHandler directly on the Compose UI, or have it as a ViewModel argument. In the complete sample code published on Github, I referenced the “PermissionHandler” via the ViewModel. The snippet below shows how to reference it directly on the Compose UI

@Composable
internal fun SampleScreen() {
val permissions = remember { listOf(Manifest.permission.CAMERA) }
val permissionsHandler = remember(permissions) { PermissionsHandler() }
val permissionsStates by permissionsHandler.state.collectAsState()
HandlePermissionsRequest(permissions = permissions, permissionsHandler = permissionsHandler)
Box {
if (permissionsStates.multiplePermissionsState?.allPermissionsGranted == true) {
Text(text = "Permission Granted")
} else {
Button (onClick = {permissionsHandler.onEvent(PermissionsHandler.Event.PermissionRequired)}) {
Text(text = "Request Permission")
}
}
}
}
view raw SampleScreen.kt hosted with ❤ by GitHub

This article was originally published on proandroiddev.com on August 01, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu