Blog Infos
Author
Published
Topics
Author
Published

Modal bottom sheets present a set of choices while blocking interaction with the rest of the screen. They are an alternative to inline menus and simple dialogs, providing additional room for content, iconography, and actions.

Getting Started

If you created your project with Empty Compose Activity template, you can skip this part but don’t forget to check versions and if necessary, update them. How to create Compose app?

//...
android {
//...
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.3.1'
}
}
dependencies {
//...
def compose_ui_version = '1.3.1'
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
implementation "androidx.compose.material:material:$compose_ui_version"
}
view raw build.gradle hosted with ❤ by GitHub
Implementation

First, we set our state variable and coroutine scope.

val coroutineScope = rememberCoroutineScope()
val modalSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
confirmStateChange = { it != ModalBottomSheetValue.HalfExpanded },
skipHalfExpanded = true,
)
view raw MainActivity.kt hosted with ❤ by GitHub

confirmStateChange is necessary to control the behavior of bottom sheet. If you set confirmStateChange = { false } user will not be able to dismiss bottom sheet by dragging or tapping outside.

skipHalfExpandedWhether the half expanded state, if the sheet is tall enough, should be skipped. If true, the sheet will always expand to the Expanded state and move to the Hidden state when hiding the sheet, either programmatically or by user interaction.

Now we will create our bottom sheet UI,

ModalBottomSheetLayout(
sheetState = modalSheetState,
sheetShape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp),
sheetContent = {
Column(
//...
) {
//...
Button(
onClick = {
coroutineScope.launch { modalSheetState.hide() }
}
) {
Text(text = "Hide Sheet")
}
}
}
) {
Scaffold {
Box(
//...
) {
Button(
onClick = {
coroutineScope.launch {
if (modalSheetState.isVisible)
modalSheetState.hide()
else
modalSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
},
) {
Text(text = "Open Sheet")
}
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

sheetShape is optional and totally up to you. I like to add it to give more rounded look to the bottom sheet.

 

 

sheetContent is where you put your bottom sheet UI. In our example we have Button which dismisses the bottom sheet on click.

As we can see from the example, we use coroutineScope to change bottom sheet state since modalSheetState.hide and modalSheetState.animateTo are suspendedfunctions.

Finally, inside of our Scaffoldwe have Button which expands bottom sheet on click.

Thats it! It’s very easy to create and manage bottom sheet with Jetpack Compose.

@Composable
fun BottomSheetLayout() {
val coroutineScope = rememberCoroutineScope()
val modalSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
confirmStateChange = { it != ModalBottomSheetValue.HalfExpanded },
skipHalfExpanded = true
)
ModalBottomSheetLayout(
sheetState = modalSheetState,
sheetContent = {
Column(
//...
) {
Button(
onClick = {
coroutineScope.launch { modalSheetState.hide() }
}
) {
Text(text = "Hide Sheet")
}
}
}
) {
Scaffold {
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Button(
onClick = {
coroutineScope.launch {
if (modalSheetState.isVisible)
modalSheetState.hide()
else
modalSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
},
) {
Text(text = "Open Sheet")
}
}
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

Extras

Dismiss On Back

BackHandler(modalSheetState.isVisible) {
coroutineScope.launch { modalSheetState.hide() }
}
view raw MainActivity.kt hosted with ❤ by GitHub

With this simple code, we can close bottom sheet on back button press if visible.

Toggle Between Fullscreen & Expanded
@Composable
fun BottomSheetLayout() {
//...
var isSheetFullScreen by remember { mutableStateOf(false) }
val roundedCornerRadius = if (isSheetFullScreen) 0.dp else 12.dp
val modifier = if (isSheetFullScreen)
Modifier
.fillMaxSize()
else
Modifier.fillMaxWidth()
ModalBottomSheetLayout(
sheetShape = RoundedCornerShape(topStart = roundedCornerRadius, topEnd = roundedCornerRadius),
sheetContent = {
Column(
modifier = modifier,
//...
) {
Button(
onClick = {
isSheetFullScreen = !isSheetFullScreen
}
) {
Text(text = "Toggle Sheet Fullscreen")
}
}
}
) {
//...
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

First, we create isSheetFullScreen state variable and set roundedCornerRadius and modifier variables which will change on isSheetFullScreen state change. e.g., if isSheetFullScreen is true corner radius will be 0 else it’ll change to 12.

Finally, we are changing the state on Button click. If you want to present your bottom sheet only full screen, you can delete all states and simply change, Modifier.fillMaxWidth to Modifier.fillMaxSize.

Full Code with Extras
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetpackComposeBottomSheetTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
BottomSheetLayout()
}
}
}
}
}
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun BottomSheetLayout() {
val coroutineScope = rememberCoroutineScope()
val modalSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
confirmStateChange = { it != ModalBottomSheetValue.HalfExpanded },
skipHalfExpanded = true
)
var isSheetFullScreen by remember { mutableStateOf(false) }
val roundedCornerRadius = if (isSheetFullScreen) 0.dp else 12.dp
val modifier = if (isSheetFullScreen)
Modifier
.fillMaxSize()
else
Modifier.fillMaxWidth()
BackHandler(modalSheetState.isVisible) {
coroutineScope.launch { modalSheetState.hide() }
}
ModalBottomSheetLayout(
sheetState = modalSheetState,
sheetShape = RoundedCornerShape(topStart = roundedCornerRadius, topEnd = roundedCornerRadius),
sheetContent = {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Button(
onClick = {
isSheetFullScreen = !isSheetFullScreen
}
) {
Text(text = "Toggle Sheet Fullscreen")
}
Button(
onClick = {
coroutineScope.launch { modalSheetState.hide() }
}
) {
Text(text = "Hide Sheet")
}
}
}
) {
Scaffold {
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Button(
onClick = {
coroutineScope.launch {
if (modalSheetState.isVisible)
modalSheetState.hide()
else
modalSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
},
) {
Text(text = "Open Sheet")
}
}
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

That’s it for this blog, I hope it was useful. 👋👋

You can contact me on,

This article was originally published on proandroiddev.com on December 06, 2022

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
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu