48

I'm trying to show a toast message when clicking on a toolbar action, but I got this error

@composable invocations can only happen from the context of an @composable function

Code:

@Composable
fun Toolbar() {
    TopAppBar(title = { Text(text = "Jetpack Compose") }, navigationIcon = {
        IconButton(onClick = {}) {
            Icon(Icons.Filled.Menu)
        }
    }, actions = {
        IconButton(onClick = {
            showMessage(message = "test")
        }) {
            Icon(vectorResource(id = R.drawable.ic_baseline_save_24))
        }
    })
}

@Preview
@Composable
fun ToolbarPreview(){
    Toolbar()
}

@Composable
fun showMessage(message:String){
    Toast.makeText(ContextAmbient.current, message, Toast.LENGTH_SHORT).show()
}
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
SNM
  • 5,625
  • 9
  • 28
  • 77

5 Answers5

44

The onClick parameter doesn't accept a composable function. Remove the @Composable annotation in the showMessage.
Use something like:

@Composable
fun Toolbar() {

    val context = LocalContext.current

    TopAppBar(title = {},
            actions = {
        IconButton(onClick = {
            showMessage(context, message = "test")
        }){}
    })
}

fun showMessage(context: Context, message:String){
    Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • 3
    Why can't you do this though? this is basic UI behaviour in any other framework? How does on display a view or change a veiw state from an onClick() event? – user901790 Jan 10 '21 at 12:36
  • Good point, unfortunately, I am still having the issue when calling show message from a LazyColumn nested in a Box. Is it because of the tree-nature of compose? – Gianluca Veschi Jan 28 '21 at 20:58
  • 6
    @user901790: Your UI should be composed based on state data, meaning the UI is never modified directly. Instead, to "modify" the UI you modify the state which in turn causes the UI to recompose and visually change. – Walter Berggren Mar 25 '21 at 14:26
  • Btw. `ContextAmbient` is now called `LocalContext` – m.reiter Jun 01 '21 at 09:44
  • @m.reiter thanks for feedback. Answer updated. – Gabriele Mariotti Jun 01 '21 at 09:58
  • How to show a AlertDialog Instead? – prat Jan 27 '23 at 16:52
  • @WalterBerggren Thanks, that was a helpful comment. Use onClick to change the compose state & the UI will be changed reactively. That is the way to go in jetpack compose. It's different from the usual javascript click listeners. – Binu Jasim Jul 30 '23 at 05:40
15

I got the same error and my solution was adding a @Composable annotation to the parameter in my method where I pass another composable function.

Example:

fun drawScreen(navigationIcon: @Composable () -> Unit) {
    navigationIcon()
    ...
}
gustavoknz
  • 482
  • 1
  • 6
  • 12
2

Here onClick() doesn't need any Composable function that's why it is throwing an error. Just remove @Composable from the showMessage function.

Bharat Lalwani
  • 1,277
  • 15
  • 17
2

If someone wants to use Composable on click event

Column {
    var isClicked by mutableStateOf(false)

    Button(onClick = { isClicked = true }) { Text(text = "Click Me") }

    if (isClicked) {
        // display your composable
    }
}
Kishore Jethava
  • 6,666
  • 5
  • 35
  • 51
1

You mean something like this?

@Composable
fun MyTopAppBar(
        title: String,
        actionItem: TopBarActionItem,
        navigationClick: (@Composable () -> Unit)? = null) {}

And how do you invoke navigationClick? The invoke() method itself is not composable and got the error again.

You can't use navigationClick?.invoke()

BaBaX Ra
  • 251
  • 1
  • 3
  • 8