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

, ,

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

With the advent of Android 15, edge-to-edge design has become the default configuration. Consequently, applications must be capable of accommodating window insets, including the system status bar and navigation bar, as well as supporting drawing…
Watch Video

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility GmbH

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android develop ...
Seven Principles Mob ...

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility ...

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
Menu