6. Sep 2021Android

Jetpack Compose Basics - Try Scaffold and put together several material components with the right layout!

Jetpack Compose can be future of Android programming. Most of it’s features and composables are easy to use and really simple to understand, others can have huge number of attributes. In Jetpack Compose Basics some of them will be explained. Scaffold will be first of them.

Peter ŠulyAndroid developer

What is Scaffold and how to use it?

Scaffold is a base composable function used in Material applications. This component provides simple way to put together several material components to construct application screen with correct layout behavior ensured. (e.g. correct position of snackbar on the top of floating action button, animations and state of drawer and more)

Scaffold contains slots for TopBar, BottomBar, Snackbar, FloatingActionButton and Drawer. It takes a lots of parameters to customize, but don't be afraid, most of them are pretty obvious and need only simple explanation.

@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
)
@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
)
  • modifier - optional parameter used for modifying properties of Scaffold root from outside
  • scaffoldState - remembered state of the screen. ScaffoldState contains informations about drawer (whether it is Opened or Closed) and snackbarHost (whether snackbar is showing). If you don't need to interact with state (e.g. showing snackbar or opening drawer) just leave it as is
  • floatingActionButtonPosition - position of floating action button, possible values are either Center or End
  • isFloatingActionButtonDocked - boolean parameter, if floating action button should overlap with bottom bar by half of it's height. This parameter is ignored if there is no bottom bar used
Jetpack Compose Scaffold Combination of floatingActionButtonPosition as Center and isFloatingActionButtonDocked as true
Combination of floatingActionButtonPosition as Center and isFloatingActionButtonDocked as true
  • drawerGestureEnabled - whether drawer can be interacted with via gestures
  • drawerShape - shape of drawer
  • drawerElevation - elevation of drawer
  • drawerBackgroundColor - background color to be used for the drawer sheet
  • drawerContentColor - color of the content to use inside the drawer sheet
  • drawerScrimColor - color of the scrim that obscures content when the drawer is open
  • backgroundColor - background color of Scaffold body, in most cases, you leave this parameter at the default value
  • contentColor - color of content in Scaffold body, in most cases, you leave this parameter at the default value

Now, it's time to discuss non-trivial but still fairly simple parameters.

TopBar

topBar: @Composable () -> Unit = {}

Optional parameter. As you can see in definition, you should provide composable function that will fill the slot at the top of the screen. Either predefined TopAppBar composable can be used or you can use your own implementation to match custom designs. Default value is empty lambda, in this case no topBar is used. This snippet of code implements TopAppBar passed to topBar parameter.

topBar = {
    TopAppBar(
        title = { Text(text = "Title text") },
        navigationIcon = {
            Icon(modifier = padding, imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
        },
        actions = {
            Icon(modifier = padding, imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
            Icon(modifier = padding, imageVector = Icons.Default.Search, contentDescription = "Search")
        }
    )
}
Jetpack Compose Usage of TopAppBar
Usage of TopAppBar

BottomBar

bottomBar: @Composable () -> Unit = {}

Optional parameter. In this case, composable function is needed that will fill the slot at the bottom of the screen. Predefined composables like BottomAppBar or BottomNavigation can be used, but these are really just recommendations. You can pass own custom composable. This snippet of code implements BottomAppBar passed to bottomBar parameter.

bottomBar = {
    BottomAppBar(
        content = {
            Icon(modifier = padding, imageVector = Icons.Default.Menu, contentDescription = "Menu")
            Icon(modifier = padding, imageVector = Icons.Default.Search, contentDescription = "Search")
            Text(text = "Anything can be here")
        }
    )
}
Jetpack Compose Usage of BottomAppBar
Usage of BottomAppBar

FloatingActionButton

floatingActionButton: @Composable () -> Unit = {}

Optional parameter. Floating action button (FAB) represents the primary action of a screen.It can be configured also by floatingActionButtonPosition (Center or End) and isFloatingActionButtonDocked (boolean, whether the FAB should overlap with BottomBar). Preferred composable for this slot is FloatingActionButton, but you can use any composable function. This snippet of code implements FloatingActionButton passed to floatingActionButton parameter.

floatingActionButton = {
    FloatingActionButton(
        onClick = {},
        content = {
            Icon(imageVector = Icons.Default.Favorite,contentDescription = "")
        }
    )
}
Jetpack Compose Floating action button with position = Center
Floating action button with position = Center
Floating action button with position = End
Floating action button with position = End

Drawer

drawerContent: @Composable (ColumnScope.() -> Unit)? = null

Optional parameter. Drawer sheet represents content that can be pulled from left (or right for RTL). Drawer can be also showed by using scaffoldState.drawerState.open() function in coroutine scope. Lambda which is needed here is already extension function of ColumnScope, so every composable which will be used in lambda will be placed like in Column (from top to bottom). Animation, scrim of background and swiping is all handled by Scaffold. This snippet of code implements 4 items passed to drawerContent parameter.

drawerContent = {
    Icon(
        modifier = Modifier.padding(16.dp),
        imageVector = Icons.Default.Person,
        contentDescription = ""
    )
    Text(modifier = Modifier.padding(16.dp), text = "First line")
    Text(modifier = Modifier.padding(16.dp), text = "Second line")
    Text(modifier = Modifier.padding(16.dp), text = "Third line")
}
Jetpack Compose Drawer

SnackbarHost

snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }

Snackbar is a little more complicated. We can use snackbarHost parameter when we want custom look of Snackbar else the default design and behavior implemented in SnackbarHost is used. We can let SnackbarHost to do it's work for now.Snackbars provide brief messages about app processes at the bottom of the screen. In our example snippet, the Snackbar is shown when FAB is clicked.

val scope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
.
.
.
scaffoldState = scaffoldState,
floatingActionButton = {
    FloatingActionButton(
        onClick = {
            scope.launch {
                scaffoldState.snackbarHostState.showSnackbar("Hello there!")
            }
        },
        content = {
            Icon(imageVector = Icons.Default.Favorite, contentDescription = "")
        }
    )
}

Few things to mention. showSnackbar(...) is suspend function thus coroutine scope is needed. We can get composition bounded one simply by using rememberCoroutineScope(). SnackbarHost guarantees to show at most one snackbar at a time, others will be queued.

Content

content: @Composable (PaddingValues) -> Unit

Remaining area on the screen. You can use any composable function here. The lambda receives PaddingValues that should be applied to the content root via Modifier.padding to properly offset top and bottom bars.

Full Scaffold with all widgets can look like this

Jetpack Compose Scaffold with all widgets
Scaffold with all widgets

More articles from the Jetpack Compose Basics series:

Peter ŠulyAndroid developer