2023/06/04
If you have worked with Jetpack Compose, you may have encountered the issue of multiple onClick events being triggered by a single button press. This can lead to unexpected behavior and crashes, frustrating both you and your users. In this article, we will explore solutions to this issue and ensure that your buttons function as expected.
To solve this problem, you can create your own button wrapper. This button’s onClick logic can only be run once. This is useful if you want to use your button for navigation or other tasks where you only want its onClick logic to be executed once.
_34@Composable_34fun ClickOnceButton(_34 onClick: () -> Unit,_34 modifier: Modifier = Modifier,_34 conditional: () -> Boolean = {true},_34 enabled: Boolean = true,_34 interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },_34 elevation: ButtonElevation? = ButtonDefaults.elevation(),_34 shape: Shape = MaterialTheme.shapes.small,_34 border: BorderStroke? = null,_34 colors: ButtonColors = ButtonDefaults.buttonColors(),_34 contentPadding: PaddingValues = ButtonDefaults.ContentPadding,_34 content: @Composable RowScope.() -> Unit_34) {_34 var onClickHasExecuted by remember { mutableStateOf(false) }_34_34 Button(_34 onClick = {_34 if (conditional() && !onClickHasExecuted) {_34 onClickHasExecuted = true_34 onClick()_34 }_34 },_34 modifier = modifier,_34 enabled = enabled,_34 interactionSource = interactionSource,_34 elevation = elevation,_34 shape = shape,_34 border = border,_34 colors = colors,_34 contentPadding = contentPadding,_34 content = content_34 )_34}
In this example, the button will only navigate the first time it is clicked, avoiding multiple navigations to the same route:
_10@Composable_10fun Example(navController: NavController = rememberNavController()){_10 ClickOnceButton(onClick = {_10 val someRoute = "some_route"_10 navController.navigate(someRoute)_10 }) {_10 Text(text = "Click me!")_10 }_10}
Now, let’s explain the conditional parameter in the wrapper. Sometimes, you may only want the logic to execute if something else is true. This optional parameter is necessary because if you implement the condition in the onClick delegate, the button won't be clickable again, even if the condition would return true on further clicks.
It’s probably easier to understand if you see it in action:
_17@Composable_17fun Example(navController: NavController = rememberNavController()) {_17 var someBoolean by remember { mutableStateOf(false) }_17_17 Button(onClick = { someBoolean = !someBoolean }) {_17 Text(text = "This toggles the boolean!")_17 }_17_17 ClickOnceButton(_17 conditional = { someBoolean },_17 onClick = {_17 val someRoute = "some_route"_17 navController.navigate(someRoute)_17 }) {_17 Text(text = "Click me!")_17 }_17}
In this example, it is not possible for the ClickOneButton to run its logic in the initial state. However, if we click the normal Button, the condition that ClickOneButton depends on is switched, making it possible to run its logic.
In conclusion, by creating a button wrapper with onClick logic that can only be executed once, we can ensure that our buttons behave as expected and avoid the frustrating issue of multiple onClick events being triggered by a single button press. By using the conditional parameter, we can further customize our button's behavior, making it a powerful tool for navigating and interacting with our app.