This is currently how I'm showing a snackbar. ```i...
# compose
c
This is currently how I'm showing a snackbar.
Copy code
if (viewModel.state.snackBarMessage.isNotEmpty()) {
    coroutineScope.launch {
        scaffoldState.snackbarHostState.showSnackbar(viewModel.state.snackBarMessage)
        viewModel.state.snackBarMessage = ""
    }
}
I get hit with a lint warning though.
Copy code
Calls to async or launch should happen inside a LaunchedEffect and not composition
Can anyone point me in the right direction over the fix? Changing the
if
with
LaunchedEffect
makes the lint error go away, but I'm not sure if that's "right"/idiomatic compose here.
a
Snack bar messages are events instead of states so you should make it a
Channel
(or
SharedFlow
depending on what you need) and collect it in a
LaunchedEffect
.
4
c
@Albert Chang thank you. Still learning! In the case where I did actually want state to create some one time "event" (so let's pretend doing a call to snackbar here would be fine), how would that work? Would I simply wrap it in a launched effect like I said?
whats interesting is compose docs show launched effect with a snackbar as an example. is this a "bad" example?
a
Would I simply wrap it in a launched effect like I said?
Yes. The problem with your code is that you are launching a new coroutine on every recomposition, which is obviously not what you want.
1
As for the example, though it may not be ideal, I won't call it bad because it is only reading
hasError
as a state. However what you are doing is setting the state from view model and clearing the state from UI. Modifying a state from both layers is absolutely a code smell.
c
Thanks for the insight. How do you know what to put in as the first arg into LaunchedEffect and whether or not to wrap the launch effect in the conditional, or put the conditional inside of the LaunchedEffect? Seems like I could do it multiple ways 1.
Copy code
LaunchedEffect (viewModel.state.snackBarMessage.isNotEmpty()) {
    coroutineScope.launch {
        scaffoldState.snackbarHostState.showSnackbar(viewModel.state.snackBarMessage)
        viewModel.state.snackBarMessage = ""
    }
}
2.
Copy code
LaunchedEffect (viewModel.state.snackBarMessage) {
if (viewModel.state.snackBarMessage.isNotEmpty()) {
    coroutineScope.launch {
        scaffoldState.snackbarHostState.showSnackbar(viewModel.state.snackBarMessage)
        viewModel.state.snackBarMessage = ""
    }
  }
}
3.
Copy code
if (viewModel.state.snackBarMessage.isNotEmpty()) {
LaunchedEffect (viewModel.state.snackBarMessage) {
    coroutineScope.launch {
        scaffoldState.snackbarHostState.showSnackbar(viewModel.state.snackBarMessage)
        viewModel.state.snackBarMessage = ""
    }
  }
}
👍 1
a
First, you should remove
coroutineScope.launch {}
as it is unnecessary and will break the cancellation of
LaunchedEffect
. If you want to show a new snack bar when the message changes, you should go with 2 or 3. They are essentially the same.
☝🏻 1
c
Thanks for teaching!
👍 1