https://kotlinlang.org logo
a

Arpit Shukla

11/09/2021, 11:11 AM
How to pass events to Composables? I have a composable
C1
which collects an event Flow from a view model.
C1
calls another composable
C2
passing some data and lambdas.
C2
contains a Scaffold. Now when my ViewModel sends an event of showing a snackbar (after doing some validation when user clicked a button), I want to pass that event to
C2
. How can I do this?
👀 1
z

Zoltan Demant

11/09/2021, 11:26 AM
I dont know if this is the correct approach, but in my case I solved it by having a 'prompt' in my state. So, user presses a button, something happens, then a prompt is added to the state, and the below code receives it. I use
collectLatest
so that a new prompt overrides the previous one right away, but you can use collect if youd like to show them one by one. The rememberUpdatedState is just so that the same LaunchedEffect block runs continiously, instead of a new one spinning up everytime a new prompt is available. The
collectLatest
shows the snackbar, and notifies my viewmodel that the prompt has been consumed so that the state is cleared of it.
Copy code
val updatedPrompt by rememberUpdatedState(prompt)

LaunchedEffect(Unit) {
    snapshotFlow { updatedPrompt }
        .filterNotNull()
        .collectLatest { current ->
            when (current) {
                is SomeMessage -> {
                // Show snackbar, notify to clear prompt
                }
            }
        }
}
a

Arpit Shukla

11/09/2021, 11:32 AM
Is this the code for
C1
or
C2
(composables in my question)?
z

Zoltan Demant

11/09/2021, 11:47 AM
Wherever you want to consume & show the snackbar 🙂
a

Arpit Shukla

11/09/2021, 11:49 AM
I receive the event in one composable
C1
and have to show the snackbar from another composable
C2
.
z

Zoltan Demant

11/09/2021, 12:05 PM
You probably need to pass in the data to
C2
somehow, in my example I would pass in the prompt. You can then use something like my code above to consume it and actually show the snackbar!
a

Albert Chang

11/09/2021, 1:45 PM
I think it’s better to hoist your state.
Copy code
@Composable
fun C1(eventFlow: Flow<String>) {
    val scaffoldState = rememberScaffoldState()
    LaunchedEffect(eventFlow, scaffoldState) {
        eventFlow.collectLatest { message ->
            scaffoldState.snackbarHostState.showSnackbar(message)
        }
    }

    C2(scaffoldState = scaffoldState)
}

@Composable
fun C2(scaffoldState: ScaffoldState) {
    Scaffold(scaffoldState = scaffoldState) {
        // content
    }
}
👍🏽 1
1
a

Adam Powell

11/09/2021, 1:58 PM
Or even go one further and hoist the snackbar state all the way into your ViewModel so you don't have to feed it from an event observer, you can just show snacks directly there.
👍 1
☝🏼 1
👍🏼 1
a

Arpit Shukla

11/09/2021, 2:11 PM
@Adam Powell If I hoist this state from my view model, I will also have to expose a lambda to change this state when the snackbar disappears after the timeout. Is that correct? Also I want my snackbar to disappear after a config change, or just be visible for the remaining duration only. But if I hoist it like this and there is a config change before the last snackbar disappeared, another snackbar will appear and will stay for another 4 seconds. How can I fix that?
a

Adam Powell

11/09/2021, 7:05 PM
SnackbarState
is defined to allow for this; the snackbar host will dismiss the specific message after the timeout. In terms of sticking around longer, arguably that's not a problem. After the screen reconfigures in a config change the user is often a bit disoriented anyway, a couple seconds more of seeing a message that they might otherwise miss isn't necessarily a negative experience. Something you could handle the timeout in a ViewModel scope or something with instead if you were so inclined but I wouldn't bother in my own code.
👍 1
👍🏽 1