12

For a android.widget.Button I can use performClick() to simulate a click programmatically. But I don't know how to do it in Jetpack Compose. I've looked into the compose.material documentation but I at least coudn't find anything about this.

6
  • I think it's not possible right now. What's your use case? Couldn't you just call the same function you call in the button's onClick instead of simulating a click? Commented Sep 12, 2021 at 8:21
  • @gpunto I want to make a demo for an app and I just can't record myself pressing buttons since it needs to be done several times and be pretty precise regarding the timing. I could just call the onClick methods but then I would not have the ripple effect, nor the sound. Commented Sep 12, 2021 at 8:29
  • It's not as straightforward as performClick, but maybe you could look into UI testing for Compose: developer.android.com/jetpack/compose/testing Commented Sep 12, 2021 at 8:32
  • Have you found a work around? I am in the same situation... Commented Nov 24, 2022 at 8:32
  • @F.Mysir I needed this some long time ago. But I remember that I used something to perform a touch event on (x,y) coordinate of the screen. Not ideal, I know. This was only for demo purposes and I had obviously only one screen size to consider. Commented Nov 24, 2022 at 22:13

4 Answers 4

11

With this solution you will get the ripple effect programmatically like you have pressed the button:

1) Create interactionSource:

val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }

2) Create coroutine scope:

val coroutine = rememberCoroutineScope()

3) In your button set interaction source:

Button(
    onClick = {
        vm.myFunction()
    },
    //important
    interactionSource = interactionSource,
    .
    .
    .
)

4) Finally call it like so from anywhere:

coroutine.launch {
    val press = PressInteraction.Press(Offset.Zero)
    interactionSource.emit(press)
    vm.myFunction()
    delay(300)
    interactionSource.emit(PressInteraction.Release(press))
}
Sign up to request clarification or add additional context in comments.

3 Comments

Nice! But the button's onClick is not invoked. Hmm
@daniyelp In our case it is better to use a viewModel and call the same function the time you launch the coroutine, I have updated my answer to be more clear.
I wouldn't say it's better. Maybe just good enough. The perfect scenario would be for that onClick method to be called when the button appears as clicked. There is no scenario in which I don't want that onClick to be called when the button appears as clicked.
6

In compose you're creating a button with action. You can pass action function and call same function programmatically.

// you need to remember your callback to prevent extra recompositions.
// pass all variables that may change between recompositions
// as keys instead of `Unit`
val onClickAction = remember(Unit) { 
    {
        
        // do some action
    }
}

LaunchedEffect(Unit) {
    // perform action in 1 sec after view appear
    delay(1000)
    onClickAction()
}
Button(
    onClick = onClickAction
) {
    Text(text = "Button")
}

6 Comments

Isn't this just like calling onClick but with extra steps?
@daniyelp I'm not sure what do you mean. LaunchedEffect is needed here because you can't call side effects directly from the view builder. Also it gives you a coroutine scope, so you can call delay and other suspend functions easily. Check out more in documentation. And remember is needed to prevent extra recompositions as I've mentioned in the comment
Sorry, I got into something yesterday. I think we are talking about different things. With performClick I can call a Button just like I would have pressed it with my finger. I could have no idea what that button does. I have a Compose Button from a library that I have made and I don't have access to its OnClickListener. I just need to press it, but not with my finger, just using the code. performClick also triggers the ripple effect.
@daniyelp so are you going to test it? Can you pass a modifier to this view?
I need to make a demo video and I have to press several buttons at fixed time intervals. I can pass modifiers to the views, but I don't know how it could help.
|
1

I'd write an instrumentation test with ComposeTestRule for this. performClick() generally works as expected for me on a SemanticNodeInteraction object.

I did run into a problem where it only worked when the element was currently visible though, so I needed to call performScrollTo() first:

composeTestRule.onNode(...).run {
    performScrollTo()
    performClick()
}

Comments

0

A very simple solution for that.

  1. Create a state variable

      val verifyNumberOnClick = remember { mutableStateOf(false) }
    
  2. Create a private function that can be used as an action for onClick.

     private fun makeSum(a: Int, b: Int){
         Log.e("Atiar", "Sum of a & b: ${a+b}")
     }
    
  3. Create if-else to run that function.

     if (verifyNumberOnClick.value){
         makeSum(5,6)
         verifyNumberOnClick.value = false
     }
    
  4. You are done. now it's your time to change the value of verifyNumberOnClick to true.

from anywhere, you will make it true your function will be called. means you can perform onclick tasks from anywhere.

as sample you can change the value of verifyNumberOnClick from button on click.

Button(
    onClick = {verifyNumberOnClick.value = false}
) {
    Text(text = "Button")
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.