Jetpack Compose: Backdrop scaffold example

Introduction

Jetpack Compose is Google's modern toolkit for building native Android UIs with declarative components. Among its many components, BackdropScaffold provides an elegant way to display content in two distinct layers: a front layer and a back layer. This UI pattern allows users to switch between the layers to access different content areas while maintaining a smooth, fluid experience. In this article, we will explore a practical implementation of BackdropScaffold in an Android app built with Kotlin and Jetpack Compose. The provided code demonstrates how to structure an app using this component, making the UI interactive and visually appealing.

In this example, we will break down the main components, including the creation of a backdrop scaffold, customizing the front and back layers, and managing the UI state with Compose. By the end of this article, you will understand how to set up a backdrop scaffold, control layer behavior, and handle user interactions effectively in your Compose application.

MainActivity: Setting the Content

The MainActivity class, as usual in Android apps, serves as the entry point. In this case, the app’s UI is entirely built using Jetpack Compose. When the activity is created, setContent is called, which sets the composable function GetScaffold as the content of the activity. This composable will define the main structure of the backdrop scaffold, including the app bar and both the front and back layers.

Inside GetScaffold, the BackdropScaffold component is initialized. The BackdropScaffold takes several parameters, including the app bar, back layer content, and front layer content. This method serves as the central structure of the UI, ensuring that the layout is dynamic and responsive.

Defining the BackdropScaffold State

The composable GetScaffold function starts by defining a scaffold state using rememberBackdropScaffoldState. This state controls the visibility of the back layer and manages transitions between the front and back layers. The initialValue of the scaffold is set to BackdropValue.Concealed, meaning the back layer starts hidden, with only the front layer visible.

Additionally, a MutableState object is used to hold the currently selected icon, which dynamically updates as users interact with the buttons on the back layer. Initially, this state is set to Icons.Filled.Favorite, and this value will change based on the user's selection.

AppBar and Layer Structure

The BackdropScaffold composable includes an app bar, which is defined using the TopAppBar composable. This app bar displays the title "Compose - Backdrop Scaffold" and features a background color of light blue. The app bar provides the familiar structure to the top of the app, giving it a professional look.

Next, the BackdropScaffold specifies both the backLayerContent and frontLayerContent. The back layer contains a set of interactive buttons, while the front layer displays content based on the user's selection from the back layer. These two layers form the foundation of this UI pattern.

Back Layer Content: User Interaction

The BackLayerContent composable defines the layout and behavior of the back layer. It consists of a column of buttons, each representing a different icon (Favorite, Email, and Phone). The content is centered inside a Box that fills the screen and is given a blue background.

Each button is created using the OutlinedButton composable. When clicked, the button changes the icon shown in the front layer and triggers a coroutine to hide (conceal) the back layer by calling backdropScaffoldState.conceal(). This interaction between the user and the UI is powered by Jetpack Compose’s state management system, making the buttons responsive and dynamic.

Front Layer Content: Dynamic Display

The FrontLayerContent composable handles the front layer's display. It uses the selectedIcon state to show the icon chosen by the user from the back layer. The content is also centered within a Box, which has a different blue shade for its background to distinguish it from the back layer.

This composable consists of an Icon and a Text element. The icon’s size is set to 250 dp, making it large and easily visible to the user, and it is tinted in a dark blue color. Below the icon, a simple text label ("Front layer content") is displayed. The dynamic relationship between the back and front layers enhances the interactivity and visual feedback of the UI.

Summary

The example code demonstrates the use of BackdropScaffold in Jetpack Compose to create a dynamic, two-layer UI with a clear separation of front and back layer content. The front layer is designed to change based on user input from the back layer, providing a responsive experience that is easy to implement with Compose's state handling.

By combining essential Compose elements such as Box, Column, Icon, and Text, and utilizing state management with remember and MutableState, this code offers a clean, structured approach to creating modern Android UIs. The use of BackdropScaffold is particularly useful for apps that need to present a secondary set of actions or options while keeping the primary content visible to users.


MainActivity.kt

package com.cfsuman.jetpackcompose

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Phone
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedObjectState: Bundle?) {
        super.onCreate(savedObjectState)
        setContent {
            GetScaffold()
        }
    }


    @OptIn(ExperimentalMaterialApi::class)
    @Composable
    fun GetScaffold(){
        val backdropScaffoldState = rememberBackdropScaffoldState(
            initialValue = BackdropValue.Concealed)
        val selectedIcon = remember {
            mutableStateOf(Icons.Filled.Favorite)}

        BackdropScaffold(
            scaffoldState = backdropScaffoldState,
            appBar = {
                TopAppBar(
                    title = { Text(
                        text = "Compose - Backdrop Scaffold"
                    )},
                    backgroundColor = Color(0xFF1ca9c9),
                )
            },
            backLayerContent = {BackLayerContent(
                backdropScaffoldState, selectedIcon
            )},
            frontLayerContent = { FrontLayerContent(
                backdropScaffoldState, selectedIcon) },
        )
    }


    @OptIn(ExperimentalMaterialApi::class)
    @Composable
    fun BackLayerContent(
        backdropScaffoldState: BackdropScaffoldState,
        selectedIcon: MutableState<ImageVector>
    ){
        val scope = rememberCoroutineScope()
        Box(
            modifier = Modifier.background(Color(0xFF5D8AA8))
                .fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Column(
                verticalArrangement = Arrangement.spacedBy(12.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                OutlinedButton(onClick = {
                    selectedIcon.value = Icons.Filled.Favorite
                    scope.launch {
                        backdropScaffoldState.conceal()
                    }
                }) {
                    Icon(Icons.Filled.Favorite, "")
                    Text(text = "Favorite")
                }

                OutlinedButton(onClick = {
                    selectedIcon.value = Icons.Filled.Email
                    scope.launch {
                        backdropScaffoldState.conceal()
                    }
                }) {
                    Icon(Icons.Filled.Email, "")
                    Text(text = "Email")
                }

                OutlinedButton(onClick = {
                    selectedIcon.value = Icons.Filled.Phone
                    scope.launch {
                        backdropScaffoldState.conceal()
                    }
                }) {
                    Icon(Icons.Filled.Phone, "")
                    Text(text = "Phone")
                }
            }
        }
    }
    
    
    @OptIn(ExperimentalMaterialApi::class)
    @Composable
    fun FrontLayerContent(
        backdropScaffoldState: BackdropScaffoldState,
        selectedIcon: MutableState<ImageVector>
    ){
        Box(
            modifier = Modifier.background(Color(0xFF89CFF0))
                .fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(12.dp)
            ) {
                Icon(
                    selectedIcon.value,
                    contentDescription = "",
                    Modifier.size(250.dp),
                    tint = Color(0xFF002147)
                )
                Text(text = "Front layer content")
            }
        }
    }
}
More android jetpack compose tutorials