Search code examples
androidandroid-jetpack-composeandroid-snackbar

How to show snackbars from Composables or ViewModels on a multi-module, single activity, Compose only, project


What's the best way to show a Snackbar on a multi-module, single activity, Compose only project?

This is how the project dependency graph looks like:

dependency graph

The only activity of the project is inside the app module and it just sets the NavHost as content.

Each feature module provides a list of composable screens that will be shown on the NavHost.

Each screen has its own Scaffold, so it can easily show Snackbars from the each screen's ViewModel.

There is a special feature module, feature-debug, that shows on a single screen, a list of composable provided by each feature module, that are called debug sections. It is used to allow any feature module to show automatically some settings inside the debug screen.

Each debug section has its own ViewModel so it works exactly like a Screen. But it's missing a Scaffold, since it only takes a portion of the screen:

+-------------------------+
| Debug screen            |
|-------------------------+
|                         |
| Feature A debug section |
|                         |
|------------------------ +
|                         |
| Feature B debug section |
|                         |
|------------------------ +
|                         |
| Feature C debug section |
|                         |
|------------------------ +
|                         |
| Feature D debug section |
|                         |
+------------------------ +

So I'm not sure how can I show a Snackbar on the Scaffold of the feature-debug screen, from a composable that is declared inside another feature module that has no visibility of the any class inside feature-debug.


Solution

  • CompositionLocal can be used to pass data through the composition tree implicitly.

    The first thing to do is to declare a variable that must be visible by provider and consumers (in my case, I created it inside the core-ui module):

    val LocalSnackbarHostState = compositionLocalOf<SnackbarHostState> { error("No SnackbarHostState provided") }
    

    Then the provider should wrap its children with a CompositionLocalProvider:

    val scaffoldState = rememberScaffoldState()
        CompositionLocalProvider(
            LocalSnackbarHostState provides scaffoldState.snackbarHostState
        ) {
            Scaffold(
    [...]
    

    And finally the children can grab an instance of the SnackbarHostState accessing the variable LocalSnackbarHostState:

    val snackbarHostState = LocalSnackbarHostState.current