Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent an onClick callback from being called multiple times?

While testing my app I realized that if a user pressed the FloatingActionButton quickly, several times, the onClick call back could be fired multiple times. In my case, this caused the backstack being popped multiple times, as the onPopBackStack callback bubbles up to the NavHost where it has access to the navController and ivokes the popBackStack method.

fun AddEditTodoScreen(onPopBackStack: () -> Unit, viewModel: AddEditTodoViewModel = viewModel()) {

    var isNavigating by remember{
        mutableStateOf(false)
    }

    LaunchedEffect(key1 = true){
        viewModel.uiEvent.collect{event : UiEvent ->
            when(event){
                is UiEvent.PopBackStack -> onPopBackStack
                else -> Unit
            }
        }
    }

    Scaffold(floatingActionButton = {
        FloatingActionButton(onClick = {
            if(!isNavigating){
                isNavigating = !isNavigating
                onPopBackStack()
            }
        }) {
            Icon(imageVector = Icons.Default.Check, contentDescription = "Check")
        }

Currently I'm just setting a isNavigating to true when the FloatingActionButton is first clicked and if clicked again, it checks if the isNavigating flag is set to true, if so it does nothing. What would be a better way, if any, to address this?

like image 949
Mauricio Benjamin Mossi Avatar asked Dec 05 '25 20:12

Mauricio Benjamin Mossi


2 Answers

Navigation already tells you if you're navigating to a new destination: the Lifecycle of the NavBackStackEntry is synchronously changed when you call navigate, moving you down from the RESUMED state.

Therefore if you want to avoid handling clicks after you've already started navigating to another screen, you'd want to check the LocalLifecycleOwner's state:

// This corresponds with the NavBackStackEntry
// associated with this screen
val lifecycleOwner = LocalLifecycleOwner.current

FloatingActionButton(onClick = {
    // Get the current state of the Lifecycle
    val currentState = lifecycleOwner.lifecycle.currentState

    // And ignore click events when you've started navigating
    // to another screen
    if (currentState.isAtLeast(Lifecycle.State.RESUMED)) {
        onPopBackStack()
    }
}

As of Lifecycle 2.8.0, the dropUnlessResumed API does this with just a single line of code:


FloatingActionButton(onClick = dropUnlessResumed {
    onPopBackStack()
}
like image 125
ianhanniballake Avatar answered Dec 10 '25 14:12

ianhanniballake


Here is an extension method that checks the current navigation lifecycle state and navigates or ignores the popBackStack() call.

/**
 * Attempts to pop the controller's back stack.
 * It will check the current lifecycle and only allow the pop
 * if the current state is RESUMED.
 *
 * See [reference](https://github.com/google/accompanist/issues/1408#issuecomment-1673011548)
 */
fun NavController.popBackStackOrIgnore() {
    if (currentBackStackEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED) {
        popBackStack()
    }
}
like image 45
Mahmudul Hasan Shohag Avatar answered Dec 10 '25 15:12

Mahmudul Hasan Shohag



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!