Blog Infos
Author
Published
Topics
Published

BottomSheet wrapping inner screen vs covering tabs as well

BC
Bottom sheet modal in Compose
@Composable
fun Screen1() {
    var showDialog by remember { mutableStateOf(false) }
  
    Scaffold {
        // Screen UI
        showDialog = true
    )

    if (showDialog) {
        Dialog(...)
    }
}

While the ModalBottomSheetLayout is implemented like this:

@Composable
fun Screen1() {
    val scope = rememberCoroutineScope()
    val bottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
  
    ModalBottomSheetLayout(
        sheetState = bottomSheetState,
        sheetContent = {
            // Bottomsheet UI
        },
        content = {
            Scaffold {
                // Screen UI
                scope.launch { bottomSheetState.show() }
            }
        },
    )
}

When opened, both of these will cover the Scaffold content with either a Dialog or a modal bottom sheet.

 

Bottom sheet is opened via Toolbar actions when the Scaffold covers the entire screen

 

It’s pretty easy to use, but what happens when your screen is used inside tab navigation? Then your code becomes more nested and might look something more like this:

@Composable
fun TabsScreen() {
    Scaffold(
        bottomBar = {
            NavigationBar(...) // or BottomNavigation in Material2
        }
    ) {
        NavHost(...) {
            composable(...) {
                Screen1()
            }
            composable(...) {
                Screen2()
            }
        }
    }
}

If you used a Dialog inside Screen1/2 it would still show up covering the entire screen (even the tabs). But, if you went with a bottom sheet, the bottom sheet won’t cover the entire screen, but only the inner Scaffold (ex. Screen1Screen2, etc above)

 

The bottom sheet is opened via Toolbar actions when the Scaffold now sits inside another Scaffold with tabs

 

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

:app

├──► :home

├──► :rounds

├──► :players

└──► :menu

 

Hacky solutions to the rescue 🎉
typealias SheetContent = @Composable ColumnScope.() -> Unit

Then I added 2 functions to each inner screen Composable:

showBottomSheet: (SheetContent) -> Unit,
hideBottomSheet: () -> Unit,

In the top-level Scaffold, I added the implementation for these 2 functions:

val showBottomSheet: (SheetContent) -> Unit = { content: SheetContent ->
    bottomSheetContent = content
    scope.launch { bottomSheetState.show() }
}
val hideBottomSheet: () -> Unit = {
    scope.launch {
        bottomSheetState.hide()
        bottomSheetContent = null
    }
}

And implemented the bottom sheet content like so:

@Composable
fun TabsScreen() {
    val bottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
    var bottomSheetContent: SheetContent? by remember { mutableStateOf(null) }

    Scaffold(
        sheetContent = {
            bottomSheetContent?.invoke(this)
        },
        sheetState = bottomSheetState,
        bottomBar = {
            NavigationBar(...) // or BottomNavigation in Material2
        }
...

With this, I’m now able to control (show/hide) the bottom sheet from each inner screen, while keeping the bottom sheet Composable logic contained next to the parent Composable inside each sub-module. The complete logic looks like this:

@Composable
fun TabsScreen() {
    val bottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
    var bottomSheetContent: SheetContent? by remember { mutableStateOf(null) }
    
    val showBottomSheet: (SheetContent) -> Unit = { content: SheetContent ->
        bottomSheetContent = content
        scope.launch { bottomSheetState.show() }
    }
    val hideBottomSheet: () -> Unit = {
        scope.launch {
            bottomSheetState.hide()
            bottomSheetContent = null
        }
    }

    BackHandler(bottomSheetContent != null) {
        hideBottomSheet()
    }

    Scaffold(
        sheetContent = {
            bottomSheetContent?.invoke(this)
        },
        sheetState = bottomSheetState,
        bottomBar = {
            NavigationBar(...) // or BottomNavigation in Material2
        }
    ) {
        NavHost(...) {
            composable(...) {
                Screen1(
                    showBottomSheet = showBottomSheet,
                    hideBottomSheet = hideBottomSheet,
                )
            }
            composable(...) {
                Screen2(
                    showBottomSheet = showBottomSheet,
                    hideBottomSheet = hideBottomSheet,
                )
            }
            ...
        }
    }
}
@Composable
fun Screen1(
    showBottomSheet: (SheetContent) -> Unit,
    hideBottomSheet: () -> Unit,
) {
      Scaffold {
          // Screen UI
          ...
          showBottomSheet {
            // Bottomsheet UI
          }
      }
}
To hack or not to hack
Thanks for reading!
Sources

This article was originally published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
Let’s suppose that for some reason we are interested in doing some tests with…
READ MORE
Menu