I often need to block the complete UI for user inp...
# compose
t
I often need to block the complete UI for user input. E.g. when user pressed a button and a download need to be done before continue. And i want to avoid double press or pressing other buttons. My approach would be to Overlap the view with an invisible composable (maybe with a progress indicator) which catches all interactions. Are there any best practices how to do this?
f
I would just disable the button for the loading period and have general loading indicator. The loading does not have to be specific to the button.
a
I would have a separate loading state and update state of the composable to loading on button click (on attempting to start the download). The composable should recompose when the state changes, check the state inside your composable and display loading indicator accordingly. more info on best practices - https://developer.android.com/jetpack/compose/state
z
An overlay composable like that is basically the technique the drawers use for their scrims, so that seems reasonable. It blocks accessibility focus for underlying components too.
t
Thanks @Zach Klippenstein (he/him) [MOD] i will have a look how the drawer implementation does it.
Thx also @Filip Wiesner and @Aditya Thakar for answering. This are of course the correct ways of implementing it when you only have one button on the screen. But i need to disable the complete screen during loading not only a single button. Of course i could implement a state for this and disable every button. But this means i have to change a lot of code. And every time i add a new button there is a risk i forget to disable it during this loading state.
a
@Timo Drick you could have separate composable for content (your buttons) and loading (progress indicator) then based on the state you could decide which one to show with a when statement
t
I want to avoid filckering in the UI so when the download takes less than 300 ms i do not want to show a loading spinner or change the UI. So only one transition is visible when the download is finished. But when the download takes more than 300 ms i am showing the loading spinner but often i do not want to change the complete screen just show an overlay. But yes i do it this way now that i just show a Invisible Box in over the complete area which should be disabled.
👍 1
So my current solution is following:
Copy code
Box() {
    content(myContent)
    if (blockUI) Box(Modifier.matchParentSize().pointerInput(onDismissRequest) { detectTapGestures {  } })
}
Ok here a more elaborated version of the overlay:
Copy code
/**
 * Locks the UI behind and after 300 ms it overlays the screen with the defined color and show a progress indicator.
 */
@Composable
private fun ProgressScrim(
    modifier: Modifier = Modifier,
    color: Color = Color.Black,
    onDismiss: () -> Unit = {}
) {
    var showScrim by remember { mutableStateOf(false) }
    LaunchedEffect(key1 = onDismiss) {
        delay(300)
        showScrim = true
    }
    val alpha by animateFloatAsState(
        targetValue = if (showScrim) .8f else 0f,
        animationSpec = TweenSpec()
    )
    Box(
        modifier
            .pointerInput(onDismiss) {
                detectTapGestures { onDismiss() }
            }
            .semantics(mergeDescendants = true) {
                contentDescription = "Progress overlay"
                onClick { onDismiss(); true }
            },
        contentAlignment = Alignment.Center
    ) {
        if (showScrim) {
            Canvas(Modifier.fillMaxSize()) {
                drawRect(color = color, alpha = alpha)
            }
            CircularProgressIndicator()
        }
    }
}
👍 1