Jetpack Compose Scaffold

Creating well-structured user interfaces for Android applications has always been a challenge for developers. With the introduction of Jetpack Compose, Google's modern UI toolkit for Android, building beautiful and functional interfaces has become more intuitive and efficient. At the heart of Compose's layout system lies the Scaffold component, a fundamental building block that helps you implement the basic material design visual layout structure. This powerful component provides a framework for positioning essential UI elements like top app bars, bottom navigation, floating action buttons, and more, allowing you to create coherent, material-design compliant applications with minimal effort.

What is Jetpack Compose Scaffold?

The Scaffold component in Jetpack Compose provides a layout structure that follows Material Design guidelines. It's designed to be the top-level container of your screen that helps you arrange other UI components in a standardized way.

Scaffold(  
    topBar = { /* Your top app bar content */ },  
    bottomBar = { /* Your bottom bar content */ },  
    floatingActionButton = { /* Your FAB content */ },  
    content = { paddingValues -> /* Your main content goes here */ }  
)  

Think of Scaffold as a blueprint for your screen, providing dedicated slots for the most common UI elements found in modern mobile applications.

Key Properties of Scaffold

1. TopBar Property

The topBar parameter allows you to add a top app bar to your screen, typically containing the screen title, navigation icon, and action buttons.

topBar = {  
    TopAppBar(  
        title = { Text("My Application") },  
        navigationIcon = {  
            IconButton(onClick = { /* Handle navigation icon click */ }) {  
                Icon(Icons.Filled.Menu, contentDescription = "Menu")  
            }  
        }  
    )  
}  

This property helps maintain consistency across your application, making navigation intuitive for users. The TopAppBar component included here provides all the standard functionality users expect, including proper elevation, typography, and spacing.

2. BottomBar Property

The bottomBar parameter lets you add persistent navigation or actions at the bottom of the screen.

bottomBar = {  
    BottomNavigation {  
        items.forEachIndexed { index, item ->  
            BottomNavigationItem(  
                icon = { Icon(item.icon, contentDescription = item.title) },  
                label = { Text(item.title) },  
                selected = selectedItem == index,  
                onClick = { selectedItem = index }  
            )  
        }  
    }  
}  

Bottom navigation is particularly useful for switching between the top-level destinations in your app. The Scaffold's bottomBar property ensures proper positioning and behavior according to Material Design standards.

3. FloatingActionButton (FAB) Property

The floatingActionButton parameter allows you to add a floating action button to your layout, typically for promoting the primary action of a screen.

floatingActionButton = {  
    FloatingActionButton(  
        onClick = { /* Handle FAB click */ }  
    ) {  
        Icon(Icons.Filled.Add, contentDescription = "Add")  
    }  
}  

The FAB is positioned correctly by the Scaffold, adhering to Material Design guidelines for its placement and appearance.

4. FloatingActionButtonPosition Property

The floatingActionButtonPosition parameter lets you specify where the FAB should be placed on the screen.

floatingActionButtonPosition = FabPosition.End  

Available options include:

  • FabPosition.Center (centers the FAB along the bottom edge)
  • FabPosition.End (places the FAB at the end of the bottom edge, default)

This property gives you flexibility in designing your UI to best suit your application's needs.

5. IsFloatingActionButtonDocked Property

The isFloatingActionButtonDocked parameter determines whether the FAB should be docked with the bottom bar, creating a cut-out effect.

isFloatingActionButtonDocked = true  

When set to true, it creates a more integrated look between the FAB and bottom bar, following Material Design standards for this interaction.

6. DrawerContent Property

The drawerContent parameter allows you to implement a navigation drawer that slides in from the side of the screen.

drawerContent = {  
    Column(modifier = Modifier.fillMaxHeight().width(300.dp)) {  
        Text("Drawer Item 1", modifier = Modifier.padding(16.dp))  
        Text("Drawer Item 2", modifier = Modifier.padding(16.dp))  
        // More drawer items  
    }  
}  

Navigation drawers are useful for providing access to destinations in your app that aren't part of the main navigation flow, such as settings or help sections.

7. Content Property

The content parameter is where you place the main content of your screen. It receives a PaddingValues parameter that provides the insets applied by other Scaffold elements.

content = { paddingValues ->  
    Column(  
        modifier = Modifier  
            .padding(paddingValues)  
            .fillMaxSize()  
    ) {  
        // Your screen content goes here  
        Text("Hello Scaffold Content!")  
    }  
}  

It's crucial to apply the padding values to your content to ensure proper spacing around other Scaffold elements like the top app bar or bottom navigation.

8. BackgroundColor and ContentColor Properties

The backgroundColor and contentColor parameters allow you to set the background color of the Scaffold and the default content color for elements within it.

backgroundColor = MaterialTheme.colors.background,  
contentColor = MaterialTheme.colors.onBackground  

These properties help maintain a consistent color scheme throughout your application, following Material Design color principles.

9. SnackbarHost Property

The snackbarHost parameter lets you customize how Snackbars are displayed within your Scaffold.

val snackbarHostState = remember { SnackbarHostState() }  
  
Scaffold(  
    snackbarHost = { SnackbarHost(snackbarHostState) },  
    // Other properties  
)  
  
// Later, to show a snackbar:  
LaunchedEffect(key1 = true) {  
    snackbarHostState.showSnackbar("This is a snackbar message")  
}  

Snackbars are important for providing brief feedback to user actions, and the Scaffold ensures they are displayed correctly within the layout hierarchy.

Complete Example: Building a Screen with Scaffold

Let's put everything together in a complete example that showcases the power of the Scaffold component:

import androidx.compose.foundation.layout.*  
import androidx.compose.material.*  
import androidx.compose.material.icons.Icons  
import androidx.compose.material.icons.filled.*  
import androidx.compose.runtime.*  
import androidx.compose.ui.Modifier  
import androidx.compose.ui.unit.dp  
  
@Composable  
fun MyScaffoldScreen() {  
    // State for selected item in bottom navigation  
    var selectedItem by remember { mutableStateOf(0) }  
      
    // State for showing snackbar  
    val snackbarHostState = remember { SnackbarHostState() }  
    val scope = rememberCoroutineScope()  
      
    Scaffold(  
        topBar = {  
            TopAppBar(  
                title = { Text("My Scaffold App") },  
                navigationIcon = {  
                    IconButton(onClick = { /* Handle navigation icon click */ }) {  
                        Icon(Icons.Filled.Menu, contentDescription = "Menu")  
                    }  
                },  
                actions = {  
                    IconButton(onClick = { /* Handle search click */ }) {  
                        Icon(Icons.Filled.Search, contentDescription = "Search")  
                    }  
                    IconButton(onClick = { /* Handle more options click */ }) {  
                        Icon(Icons.Filled.MoreVert, contentDescription = "More")  
                    }  
                }  
            )  
        },  
        bottomBar = {  
            BottomNavigation {  
                val items = listOf(  
                    "Home" to Icons.Filled.Home,  
                    "Favorites" to Icons.Filled.Favorite,  
                    "Profile" to Icons.Filled.Person  
                )  
                  
                items.forEachIndexed { index, (title, icon) ->  
                    BottomNavigationItem(  
                        icon = { Icon(icon, contentDescription = title) },  
                        label = { Text(title) },  
                        selected = selectedItem == index,  
                        onClick = { selectedItem = index }  
                    )  
                }  
            }  
        },  
        floatingActionButton = {  
            FloatingActionButton(  
                onClick = {  
                    scope.launch {  
                        snackbarHostState.showSnackbar("FAB clicked!")  
                    }  
                }  
            ) {  
                Icon(Icons.Filled.Add, contentDescription = "Add")  
            }  
        },  
        floatingActionButtonPosition = FabPosition.End,  
        isFloatingActionButtonDocked = true,  
        snackbarHost = { SnackbarHost(snackbarHostState) },  
        drawerContent = {  
            Column(modifier = Modifier.fillMaxHeight().width(300.dp).padding(16.dp)) {  
                Text(  
                    "Drawer Header",  
                    style = MaterialTheme.typography.h6,  
                    modifier = Modifier.padding(bottom = 16.dp)  
                )  
                Divider()  
                Spacer(Modifier.height(16.dp))  
                  
                val drawerItems = listOf(  
                    "Settings" to Icons.Filled.Settings,  
                    "Help" to Icons.Filled.Help,  
                    "About" to Icons.Filled.Info  
                )  
                  
                drawerItems.forEach { (title, icon) ->  
                    Row(  
                        modifier = Modifier  
                            .fillMaxWidth()  
                            .padding(vertical = 8.dp)  
                    ) {  
                        Icon(icon, contentDescription = title)  
                        Spacer(Modifier.width(16.dp))  
                        Text(title)  
                    }  
                }  
            }  
        },  
        content = { paddingValues ->  
            // Main content area  
            Column(  
                modifier = Modifier  
                    .padding(paddingValues)  
                    .fillMaxSize()  
                    .padding(16.dp)  
            ) {  
                Text(  
                    "Scaffold Example",  
                    style = MaterialTheme.typography.h5,  
                    modifier = Modifier.padding(bottom = 16.dp)  
                )  
                  
                Text(  
                    "This is a complete example of using Scaffold in Jetpack Compose. " +  
                    "It demonstrates how to properly structure your UI using Material Design principles.",  
                    style = MaterialTheme.typography.body1  
                )  
                  
                Spacer(Modifier.height(16.dp))  
                  
                Button(  
                    onClick = {  
                        scope.launch {  
                            snackbarHostState.showSnackbar("Button clicked!")  
                        }  
                    }  
                ) {  
                    Text("Show Snackbar")  
                }  
                  
                // Content changes based on selected bottom navigation item  
                when (selectedItem) {  
                    0 -> {  
                        Spacer(Modifier.height(16.dp))  
                        Text("Home Screen Content", style = MaterialTheme.typography.h6)  
                    }  
                    1 -> {  
                        Spacer(Modifier.height(16.dp))  
                        Text("Favorites Screen Content", style = MaterialTheme.typography.h6)  
                    }  
                    2 -> {  
                        Spacer(Modifier.height(16.dp))  
                        Text("Profile Screen Content", style = MaterialTheme.typography.h6)  
                    }  
                }  
            }  
        }  
    )  
}