Getting started with jetpack compose – Scaffold layout

A Scaffold is a layout that implements the basic material design layout structure. You can add things like a TopBar, BottomBar, FAB, or a Drawer. Jetpack Compose provides Scaffold Composable which can save a lot of time. It’s like a prebuilt template.


If you’re new to Compose, I highly recommend going through the following articles:

Getting started with jetpack compose – Basic components

Getting started with jetpack compose – Layouts

Getting started with jetpack compose – Modifiers

Getting started with jetpack compose – ConstraintLayout

Getting started with jetpack compose – Theming


looks at my sample scaffold layout,

setContent {
            JetpackComposeScaffoldLayoutTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                    var toolbarTitle by remember {
                        mutableStateOf("Home")
                    }
                    val scaffoldState =
                        rememberScaffoldState(rememberDrawerState(initialValue = DrawerValue.Closed))
                    val scope = rememberCoroutineScope()

                    Scaffold(
                        modifier = Modifier.background(Color.White),
                        scaffoldState = scaffoldState,
                        topBar = {
                            AppToolbar(
                                scaffoldState = scaffoldState,
                                scope = scope,
                                toolbarTitle = toolbarTitle
                            )
                        }, drawerContent = {
                            DrawerView(scaffoldState = scaffoldState, scope = scope)
                        },
                        content = {},
                        bottomBar = { AppBottomBar() },
                        snackbarHost = { state -> MySnackHost(state) },
                        isFloatingActionButtonDocked = true,
                        floatingActionButtonPosition = FabPosition.Center,
                        floatingActionButton = { MyFloatingButton(scaffoldState) }
                        )
                }
            }
        }

and the output of this code,

scaffold layout demo

Looks very simple. let’s see all the composable in detail,

Modifier

A modifier is a way to modify certain aspects of the component that result in how it’s displayed on the screen.

I am explained modifiers in another post. Please check it.

Getting started with jetpack compose – Modifiers

scaffoldState

It maintains the state of the scaffold composable. It contains drawerState and snackbarHostState to perform operations like close, opening the drawer. Also, show and hide the snackbar.

class ScaffoldState(
    val drawerState: DrawerState,
    val snackbarHostState: SnackbarHostState
)

set the scaffoldState in scaffold composable.

 val scaffoldState = rememberScaffoldState(rememberDrawerState(initialValue = DrawerValue.Closed))
 Scaffold(modifier = Modifier.background(Color.White),
          scaffoldState = scaffoldState,
          ...
 }

rememberCoroutineScope

As LaunchedEffect is a composable function, it can only be used inside other composable functions. In order to launch a coroutine outside of a composable, but scoped so that it will be automatically canceled once it leaves the composition, use rememberCoroutineScope.

rememberCoroutineScope is a composable function that returns a CoroutineScope bound to the point of the Composition where it’s called. The scope will be canceled when the call leaves the Composition.

For example, display s snackbar we need to use remeberCoroutineScope,

val scope = rememberCoroutineScope()
    FloatingActionButton(onClick = {
        scope.launch {
            when (scaffoldState.snackbarHostState.showSnackbar(
                message = "Jetpack Compose Snackbar",
                actionLabel = "Ok"
            )) {
                SnackbarResult.Dismissed ->
                    Log.d("TAB", "Dismissed")
                SnackbarResult.ActionPerformed ->
                    Log.d("TAB", "Action!")
            }
        }
    }) {
        Text("X")
    }

TopAppBar

It’s a simple composable function to design the top bar of the app.

Let’s see the function of the TopAppBar:

@Composable
fun TopAppBar(
title: @Composable () -> Unit,
modifier: Modifier = Modifier,
navigationIcon: @Composable (() -> Unit)? = null,
actions: @Composable RowScope.() -> Unit = {},
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = AppBarDefaults.TopAppBarElevation
)

Parameters

title – provide the title text for the TopAppBar.

title = { Text(text = "Home") }

Modifier – Used to modify the background of the TopAppBar.

navigationIcon – Used to set the Navigation Icon like Drawer menu and handle the functions alike open the drawer by clicking the menu icon.

navigationIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_menu_24),
contentDescription = "Menu", modifier = Modifier.clickable {
scope.launch {
scaffoldState.drawerState.open()
}
}
)
}

actions – The actions displayed at the end of the TopAppBar. This should typically be IconButtons. The default layout here is a Row, so icons inside will be placed horizontally.

actions = {
val context = LocalContext.current
Icon(
painter = painterResource(id = R.drawable.ic_baseline_settings_24),
contentDescription = "Setting", modifier = Modifier.clickable {
Toast.makeText(context, "Open Setting", Toast.LENGTH_SHORT).show()
}
)
}

backgroundColor – Set the background color for the TopApBar.

elevation – the elevation of this TopAppBar.

This is the code for the sample I have created for the TopAppBar.

@Composable
fun AppToolbar(scaffoldState: ScaffoldState, scope: CoroutineScope, toolbarTitle: String) {
    TopAppBar(
        title = { Text(text = toolbarTitle) },
        modifier = Modifier.background(Color(0xFF008800)),
        navigationIcon = {
            Icon(
                painter = painterResource(id = R.drawable.ic_baseline_menu_24),
                contentDescription = "Menu", modifier = Modifier.clickable {
                    scope.launch {
                        scaffoldState.drawerState.open()
                    }
                }
            )
        },
        actions = {
            val context = LocalContext.current
            Icon(
                painter = painterResource(id = R.drawable.ic_baseline_settings_24),
                contentDescription = "Setting", modifier = Modifier.clickable {
                    Toast.makeText(context, "Open Setting", Toast.LENGTH_SHORT).show()
                }
            )
        }
    )
}
TopAppBar

The Drawer Content

In the drawerContent, we need to provide the content for the Drawer. content of the Drawer sheet that can be pulled from the left side.

Let check out my sample drawer composable.

@Composable
fun DrawerContent(scaffoldState: ScaffoldState, scope: CoroutineScope) {

    Column(modifier = Modifier.fillMaxWidth()) {
        Image(
            painter = painterResource(id = R.drawable.ic_baseline_face_24),
            modifier = Modifier
                .size(100.dp)
                .align(Alignment.CenterHorizontally), contentDescription = "Face"
        )

        Spacer(modifier = Modifier.padding(4.dp))
        Text(
            text = "Velmurugan",
            style = MaterialTheme.typography.subtitle1,
            modifier = Modifier.align(
                Alignment.CenterHorizontally
            )
        )
        Spacer(modifier = Modifier.padding(4.dp))
        Text(text = "User1",
            Modifier
                .fillMaxWidth()
                .background(Color.Gray)
                .padding(8.dp, 4.dp)
                .clickable {
                    scope.launch {
                        scaffoldState.drawerState.close()
                    }
                })
        Spacer(modifier = Modifier.padding(4.dp))
    }
}
Drawer layout

By using the scaffoldState we can show and close the drawer content.

scaffoldState.drawerState.open() – display the drawer

scaffoldState.drawerState.close() – close the drawer

BottomAppBar

The bottomAppBar is used to display the menu at bottom of the screen same as the bottom.

To create bottomAppBar, First, we need to create Menu items for the Bottom Navigation.

data class MenuItem(val title: String, val icon: ImageVector)

    fun getMenuList() : List<MenuItem> {
        val menuItems = mutableListOf<MenuItem>()
        menuItems.add(MenuItem(Navigations.HOME.name, Icons.Filled.Home))
        menuItems.add(MenuItem(Navigations.SEARCH.name, Icons.Filled.Search))
        menuItems.add(MenuItem(Navigations.FAVORITE.name, Icons.Filled.Favorite))
        menuItems.add(MenuItem(Navigations.SETTINGS.name, Icons.Filled.Settings))
        return menuItems
    }

    val bottomMenuList = getMenuList()

Next, we need to create BottomNavigation composable to create Menu item using the BottomNavigationItem.

@Composable
fun RowScope.BottomNavigationItem(
selected: Boolean,
onClick: () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
alwaysShowLabel: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor.copy(alpha = ContentAlpha.medium)
)

Parameters

selected – set the select state for the bottom navigation menu item.

onClick() – set the click listener for the menu item click.

icon:@Composable() – set the icon for the bottom navigation menu item.

icon = {
    Icon(
        imageVector = bottomMenu.icon,
        contentDescription = bottomMenu.title
    )
}

label – add the label for the menu item.

alwaysShowLabel: Boolean – show and hide the menu item label.

selectedContentColor: Color – content color for the selected menu item

unselectedContentColor: Color – content color for the unselected menu item.

Let’s see the example BottomAppBar,

@Composable
fun BottomAppBar() {
    data class MenuItem(val title: String, val icon: ImageVector)

    fun getMenuList() : List<MenuItem> {
        val menuItems = mutableListOf<MenuItem>()
        menuItems.add(MenuItem(Navigations.HOME.name, Icons.Filled.Home))
        menuItems.add(MenuItem(Navigations.SEARCH.name, Icons.Filled.Search))
        menuItems.add(MenuItem(Navigations.FAVORITE.name, Icons.Filled.Favorite))
        menuItems.add(MenuItem(Navigations.SETTINGS.name, Icons.Filled.Settings))
        return menuItems
    }

    val bottomMenuList = getMenuList()

    BottomNavigation {
        bottomMenuList.forEach { bottomMenu ->
            BottomNavigationItem(
                selected = bottomMenu.title == "Home",
                onClick = {
                },
                icon = {
                    Icon(
                        imageVector = bottomMenu.icon,
                        contentDescription = bottomMenu.title
                    )
                })
        }
    }
}
BottomAppBar design

FloatingActionButton

we can create FAB using the FloatingActionButton composable function.

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FloatingActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
backgroundColor: Color = MaterialTheme.colors.secondary,
contentColor: Color = contentColorFor(backgroundColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
content: @Composable () -> Unit
)

Parameters

onClick() – set click listener for the FAB.

modifier – add view modification for the FAB.

shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)) – Provide the shape for the FAB.

backgroundColor – set the background color for the FAB.

contentColor – set the content color on the FAB.

content – provide the content composable for the FAB.

Let’s see the sample FAB,

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FloatingActionButtonSample() {
    FloatingActionButton(onClick = {
        
    }) {
        Text("+")
    }
}
FAB design

FloatingActionButtonPosition

When you have added a FAB, you can use FloatingActionButtonPosition to specify its position. The default position is at the end of your layout.

Now, I changed the position to the center using,

floatingActionButtonPosition = FabPosition.Center
BottomAppBar with FAB in center

Snackbar

By default, a Snackbar is provided, and we can just call it to show without setting it up. However, if we want to customize it, we can still set it up and customize it as below.

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MySnackHost(state: SnackbarHostState) {
    SnackbarHost(
        state,
        snackbar = { data ->
            Snackbar(
                modifier = Modifier
                    .padding(8.dp)
                    .background(Color.Black, RoundedCornerShape(8.dp)),
                action = {
                    Text(
                        text = "HIDE",
                        modifier = Modifier
                            .padding(8.dp)
                            .clickable {
                                state.currentSnackbarData?.dismiss()
                            },
                        style = androidx.compose.ui.text.TextStyle(
                            fontWeight = FontWeight.Bold,
                            color = MaterialTheme.colors.primary,
                            fontSize = 18.sp
                        )
                    )
                }
            ) {
                Text(text = data.message)
            }
        })
}

Now we can set the customized snackbar into the scaffold.

Scaffold(
                        modifier = Modifier.background(Color.White),
                        scaffoldState = scaffoldState,
                        topBar = {
                        }, drawerContent = {
                            
                        },
                        content = {},
                        bottomBar = { BottomAppBar() },
                        snackbarHost = { state -> MySnackHost(state) },
                        isFloatingActionButtonDocked = true,
                        floatingActionButtonPosition = FabPosition.Center,
                        floatingActionButton = { FloatingActionButtonSample() }
                    )

Now, we set the custom snackbar into the scaffold layout.

The next step is to display the snackbar using the scaffoldState.

whenever we need to show the snackbar, we need to pass the message and the click action.

I have created a FAB composable, a click of the FAB, I am displaying the snackbar. please check the code below,

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FloatingActionButtonSample(scaffoldState: ScaffoldState) {
    val scope = rememberCoroutineScope()
    FloatingActionButton(onClick = {
        scope.launch {
            when (scaffoldState.snackbarHostState.showSnackbar(
                message = "Snackbar",
                actionLabel = "Ok"
            )) {
                SnackbarResult.Dismissed ->
                    Log.d("TAB", "Dismissed")
                SnackbarResult.ActionPerformed ->
                    Log.d("TAB", "Action!")
            }
        }
    }) {
        Text("X")
    }
}
Snackbar demo

That’s it. Thanks for reading.

You can download this example on GitHub.

Leave a Reply

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