Search code examples
androidkotlinandroid-jetpack-composeandroid-snackbar

Global Snackbar Handling Jetpack Compose


Seeing as I have multiple places where snackbars could be triggered, I want to have a central place in my app where I can handle showing/dismissing snackbars.

This is the structure of my app:

app structure

I've implemented a BaseViewModel that contains a StateFlow which should keep track of the SnackBar message (every other ViewModel inherits from this BaseViewModel):

@HiltViewModel
open class BaseViewModel @Inject constructor() : ViewModel() {
    val _snackBarMessage = MutableStateFlow("")
    val snackBarMessage: StateFlow<String> = _snackBarMessage
}

To test if the update of the StateFlow is triggered correctly, I've implemented a message that should update the StateFlow after every login:

private fun setSnackBarMessage() {
   _snackBarMessage.value = "A wild snackBar appeared"
}

MainContent contains my Scaffold (incl. scaffoldState, snackbarHost), should react to changes in the snackBarMessage flow and display/dismiss the Snackbar when needed:

fun MainContent(...){
   val message by viewModel.snackBarMessage.collectAsState()

   LaunchedEffect(message) {
      if (message.isNotEmpty() Timber.d("We got a snackbar")
   }

   Scaffold(...){...}
}

During debugging, I noticed that after every login the snackBarMessage value is updated correctly but MainContent does not get those updates which, in turn, means that the snackbar is never displayed.

Is there a reason why MainContent does not get those updates from the LoginComposable? Is it even possible to have a central instance of a snackbar or do I really need to handle snackbars separately in every Composable?


Solution

  • You can use this
    
    @Composable
    fun MainScreen() {
        val coroutineScope = rememberCoroutineScope()
        val showSnackBar: (
            message: String?,
            actionLabel: String,
            actionPerformed: () -> Unit,
            dismissed: () -> Unit
        ) -> Unit = { message, actionLabel, actionPerformed, dismissed ->
            coroutineScope.launch {
                val snackBarResult = scaffoldState.snackbarHostState.showSnackbar(
                    message = message.toString(),
                    actionLabel = actionLabel
                )
                when (snackBarResult) {
                    SnackbarResult.ActionPerformed -> actionPerformed.invoke()
                    SnackbarResult.Dismissed -> dismissed.invoke()
                }
            }
        }
    
    
        //Global using
        showSnackBar.invoke(
            "YOUR_MESSAGE",
            "ACTION_LABEL",
            {
             //TODO ON ACTION PERFORMED
            },
            {
             //TODO ON DISMISSED
            }
        )
    }