Jetpack Compose Bottom Navigation + Nested Navigation Solved

saurabh jadhavsaurabh jadhav
14 min read

In today's discussion, we delve into implementing bottom navigation in Jetpack Compose, which can be particularly challenging when combined with nested navigation. While several methods are available, developers often encounter difficulties with navigation complexities. Here, we outline three common approaches to managing bottom navigation, each with its own set of challenges and solutions.

jetpack compose bottom navigation with nested navigation

Method 1: Standard Bottom Navigation

In this approach, bottom navigation is implemented using a single navigation graph. This method allows for straightforward management of bottom navigation but does not support nested navigation effectively. The bottom navigation bar remains visible across all navigational transitions, which may not be desirable in all scenarios.

Method 2: Dual Navigation Graphs

This method involves using two separate navigation graphs. It offers more flexibility compared to the first method and allows for some level of nested navigation. However, managing two graphs can make the navigation logic more complex and hectic, leading to potential issues in state management and user experience.

Method 3: Custom Solution

In response to the limitations of the first two methods, we developed a custom solution that integrates the best aspects of both. This approach allows for robust nested navigation while keeping the bottom navigation bar's behavior flexible and context-sensitive.

Overview of the Custom Solution

Our custom solution is designed to overcome the common pitfalls associated with standard bottom navigation and dual navigation graphs. By implementing a single, unified navigation graph that dynamically adjusts the visibility and behavior of the bottom navigation bar, we provide a seamless user experience across different navigation layers.

Key Benefits:

  • Flexibility: The bottom navigation bar can be shown or hidden based on the navigation route, making it context-sensitive.

  • Simplicity in Management: Despite the underlying complexity of nested navigation, our approach simplifies navigation state management and enhances UI consistency.

  • Improved Performance: Optimized navigation transitions reduce load times and resource consumption, ensuring smoother interactions for the user.

Implementation Insights

We achieve this through careful design of our navigation architecture:

  1. Dynamic Visibility: The visibility of the bottom navigation bar is controlled by listening to navigation changes and applying rules based on the current route's requirements.

  2. Centralized Navigation Graph: All navigation routes are managed within a single graph, with conditions set for including or excluding the bottom navigation bar as needed.

This tailored approach ensures that our application handles nested navigation intuitively, avoiding the common frustrations that users and developers face with more conventional methods.

Why I Wrote This Blog

While there are numerous articles available on implementing bottom navigation in Jetpack Compose, many developers have expressed ongoing challenges with nested navigation in their comments. This feedback has inspired us to share our solution, aiming to fill the gap in existing resources and provide a practical guide that addresses these specific challenges.

By documenting our journey and the rationale behind our decisions, we hope to assist other developers in navigating these complex scenarios more effectively. This blog serves as a comprehensive resource for those looking to implement a robust and flexible navigation system in their Jetpack Compose applications.

Let's Start

As this blog is a continuation of our previous discussion on navigation in Jetpack Compose, we will directly dive into the specifics of implementing bottom navigation. For those who are joining us now, you might find it helpful to review our foundational navigation blog first: Ultimate Guide to Jetpack Compose Navigation. This will give you a comprehensive understanding of the basic concepts we are building upon.

Architecture:

Now, let's explore our new AppNavigation class, designed specifically to handle the intricacies of nested navigation while maintaining a dynamic and context-sensitive bottom navigation bar. Here is how we have structured our navigation solution:

package com.simplifymindfulness.composebottom_navigationexample

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.simplifymindfulness.composebottom_navigationexample.composables.*

@Composable
fun AppNavigation(navController: NavHostController, modifier: Modifier = Modifier) {
    NavHost(navController = navController, startDestination = "bottom_screen1") {
        // Define composable screens for bottom navigation
        composable("bottom_screen1") { BottomScreen1(navController) }
        composable("bottom_screen2") { BottomScreen2(navController) }
        // Additional screens not included in the bottom bar
        composable("screen1") { Screen1(navController) }
        composable("screen2") { Screen2(navController) }
        composable("screen3") { Screen3(navController) }
        composable("screen4/{data}", arguments = listOf(navArgument("data") { type = NavType.StringType })) { backStackEntry ->
            Screen4(navController, backStackEntry.arguments?.getString("data") ?: "")
        }
    }
}

This AppNavigation class showcases how we manage both the visible screens linked directly via the bottom navigation bar and additional screens that are part of nested navigational flows. The flexibility to hide or show the bottom navigation bar based on the user's current navigation path allows for a smoother and more intuitive user experience.

By focusing on a unified navigation graph with dynamic response capabilities, we ensure that our application remains user-friendly and responsive to the varied needs of different screens and user interactions. This approach mitigates common challenges associated with static bottom navigation bars and rigid navigation structures.

Let's talk about the two new screens we've added to the bottom navigation in our app: BottomScreen1 and BottomScreen2. These screens are super simple and show how easily we can manage different navigation flows in our app.

BottomScreen1 introduces a bit of interaction by guiding users through nested navigation. Here's what it looks like:

@Composable
fun BottomScreen1(navController: NavController) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp)
            .background(MaterialTheme.colorScheme.surface),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            "Welcome to Screen 1! Click the button below for nested navigation. The bottom navigation bar will be hidden.",
            style = MaterialTheme.typography.headlineSmall.copy(fontSize = 18.sp)
        )

        Button(onClick = { navController.navigate("screen1") }) {
            Text("Go to Screen 1")
        }
    }
}

And then there's BottomScreen2, which keeps things chill with a straightforward display:

@Composable
fun BottomScreen2(navController: NavController) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp)
            .background(MaterialTheme.colorScheme.background),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            "This is the Bottom Screen 2", 
            style = MaterialTheme.typography.headlineSmall.copy(fontSize = 18.sp)
        )
    }
}

These two screens help demonstrate how you can control navigation and screen content in a clean and intuitive way.

We've also set up the navigation items and routes for our bottom navigation bar. Here's a quick look at what we created:

val bottomNavigationItems = listOf(
    NavigationItem("bottom_screen1", "Screen 1", Icons.Filled.Home),
    NavigationItem("bottom_screen2", "Screen 2", Icons.Filled.Settings)
)

val bottomBarRoutes = setOf("bottom_screen1", "bottom_screen2")

This setup defines the items for our bottom navigation, associating each screen with an icon and a route. These help manage which screens are included in the bottom navigation and ensure a smooth flow between them.

Overview of MainScreen

The MainScreen function is the central hub for managing the navigation within our app. It uses several key Compose and Navigation components:

@Composable
fun MainScreen(navController: NavHostController) {
    // Navigation and UI code here
}

Navigation Controller and Back Stack Entry

navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
  • navBackStackEntry: This keeps track of the navigation stack's current state, allowing the app to know which screen or destination is active.

  • currentRoute: Extracts the current route from the navigation stack, used to determine which navigation items should be shown as selected.

Scaffold with Bottom Navigation Bar

Scaffold(
    bottomBar = {
        if (currentRoute in bottomBarRoutes) {
            NavigationBar {
                bottomNavigationItems.forEach { item ->
                    NavigationBarItem(
                        icon = { Icon(item.icon, contentDescription = null) },
                        label = { Text(item.label) },
                        selected = currentRoute == item.route,
                        onClick = {
                            if (currentRoute != item.route) {
                                navController.navigate(item.route) {
                                    popUpTo(navController.graph.startDestinationId)
                                    launchSingleTop = true
                                }
                            }
                        }
                    )
                }
            }
        }
    }
) { innerPadding ->
    AppNavigation(navController, Modifier.padding(innerPadding))
}
  • Scaffold: Provides a basic material design layout structure for the app, including places for a top bar, bottom bar, floating action button, etc.

  • Bottom Navigation Bar: Conditionally displayed based on whether the current route is in the bottomBarRoutes set.

    • NavigationBar: Holds the individual navigation items.

    • NavigationBarItem: Each item represents a clickable icon and label in the bottom bar. It is marked as selected if the current route matches the item’s route, enhancing user orientation.

Navigation Items and Routes

val bottomNavigationItems = listOf(
    NavigationItem("bottom_screen1", "Screen 1", Icons.Filled.Home),
    NavigationItem("bottom_screen2", "Screen 2", Icons.Filled.Settings)
)

val bottomBarRoutes = setOf("bottom_screen1", "bottom_screen2")
  • bottomNavigationItems: Defines the content of the bottom bar, linking each item to a specific route and icon.

  • bottomBarRoutes: Helps determine when the bottom navigation bar should be visible, based on the current route.

Data Class for Navigation Items

data class NavigationItem(val route: String, val label: String, val icon: ImageVector)
  • NavigationItem: A data class that encapsulates information about navigation items, such as the route, label, and associated icon. This structure simplifies managing navigation elements throughout the app.

Explanation in Blocks

Here's a visual breakdown:

  • MainScreen ➜ Acts as the shell containing the navigation structure.

  • Scaffold ➜ Manages the layout.

    • NavigationBar ➜ Shows the navigation items.

      • NavigationBarItem ➜ Represents each clickable navigation link.

Custom Approach to Bottom Nav Visibility

Our technique using if (currentRoute in bottomBarRoutes) isn't the standard approach, but it has proven effective for simplifying code readability and enhancing user interactions. Here's a snippet that illustrates this method:

if (currentRoute in bottomBarRoutes) {
    // Show the navigation bar
    NavigationBar {
        bottomNavigationItems.forEach { item ->
            NavigationBarItem(
                icon = { Icon(item.icon, contentDescription = null) },
                label = { Text(item.label) },
                selected = currentRoute == item.route,
                onClick = {
                    if (currentRoute != item.route) {
                        navController.navigate(item.route) {
                            popUpTo(navController.graph.startDestinationId)
                            launchSingleTop = true
                        }
                    }
                }
            )
        }
    }
}

How Others Handle It

As Already Mentioned, In contrast, the first two common methods for implementing bottom navigation in Jetpack Compose often stick closer to the guidelines but may fall short when handling nested navigation effectively:

  • Method 1: Uses a single navigation graph with a persistent bottom navigation bar. While straightforward, this method doesn't allow the bottom navigation bar to be hidden or adjusted based on nested navigation paths, which can lead to a less flexible user experience.

  • Method 2: Implements dual navigation graphs, aiming to provide flexibility by separating the primary navigation from secondary or nested flows. This can sometimes make the navigation complex and harder to manage, particularly when trying to synchronize states between the two graphs or when the nested navigation needs to influence the visibility of the bottom navigation bar.

The Challenge with Standard Methods

Both standard methods sometimes struggle with nested navigation scenarios where the bottom navigation bar needs to be context-sensitive or temporarily hidden. These methods can lead to:

  • Reduced Flexibility: Difficulty in adapting the UI dynamically based on user navigation paths.

  • Complexity in State Management: Increased complexity in managing navigation states, especially when transitions between primary and nested navigations occur.

Our custom method addresses these issues by allowing dynamic response to navigation changes, thus enhancing the overall user experience and keeping the interface clean and focused on current user needs.

Wrapping Up Our Bottom Navigation Setup

Those were the steps to implement a bottom navigation system that works seamlessly with nested navigation graphs in Jetpack Compose. This approach gives you the flexibility to show or hide the navigation bar based on the user's current context, improving the app's usability and your code's maintainability.

Now, I'll share the complete code for our approach below, so you can see how all these pieces fit together. If you want to dive deeper or tweak the setup for your own projects, you can find the full project on GitHub.

Full Code:

Project Gradle:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.androidApplication) apply false
    alias(libs.plugins.jetbrainsKotlinAndroid) apply false
}

Build Gradle:

plugins {
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.jetbrainsKotlinAndroid)
}

android {
    namespace 'com.simplifymindfulness.composebottom_navigationexample'
    compileSdk 34

    defaultConfig {
        applicationId "com.simplifymindfulness.composebottom_navigationexample"
        minSdk 24
        targetSdk 34
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.5.1'
    }
    packaging {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {

    implementation libs.androidx.core.ktx
    implementation libs.androidx.lifecycle.runtime.ktx
    implementation libs.androidx.activity.compose
    implementation platform(libs.androidx.compose.bom)
    implementation libs.androidx.ui
    implementation libs.androidx.ui.graphics
    implementation libs.androidx.ui.tooling.preview
    implementation libs.androidx.material3
    testImplementation libs.junit
    androidTestImplementation libs.androidx.junit
    androidTestImplementation libs.androidx.espresso.core
    androidTestImplementation platform(libs.androidx.compose.bom)
    androidTestImplementation libs.androidx.ui.test.junit4
    debugImplementation libs.androidx.ui.tooling
    debugImplementation libs.androidx.ui.test.manifest

    //navigation
    implementation libs.androidx.navigation.compose

}

libs.versions.toml :

[versions]
agp = "8.3.2"
kotlin = "1.9.0"
coreKtx = "1.13.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.7.0"
activityCompose = "1.9.0"
composeBom = "2023.08.00"
navigationCompose = "2.7.7"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

MainActivity :

package com.simplifymindfulness.composebottom_navigationexample

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController
import com.simplifymindfulness.composebottom_navigationexample.composables.MainScreen
import com.simplifymindfulness.composebottom_navigationexample.ui.theme.ComposeBottomNavigationExampleTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            ComposeBottomNavigationExampleTheme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    MainScreen(navController)
                }
            }
        }
    }
}

AppNavigation.kt :

package com.simplifymindfulness.composebottom_navigationexample

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.simplifymindfulness.composebottom_navigationexample.composables.BottomScreen1
import com.simplifymindfulness.composebottom_navigationexample.composables.BottomScreen2
import com.simplifymindfulness.composebottom_navigationexample.composables.NavigationItem
import com.simplifymindfulness.composebottom_navigationexample.composables.Screen1
import com.simplifymindfulness.composebottom_navigationexample.composables.Screen2
import com.simplifymindfulness.composebottom_navigationexample.composables.Screen3
import com.simplifymindfulness.composebottom_navigationexample.composables.Screen4

@Composable
fun AppNavigation(navController: NavHostController, modifier: Modifier = Modifier) {
    NavHost(navController = navController, startDestination = "bottom_screen1") {
        composable("bottom_screen1") { BottomScreen1(navController) }
        composable("bottom_screen2") { BottomScreen2(navController) }
        composable("screen1") { Screen1(navController) }
        composable("screen2") { Screen2(navController) }
        composable("screen3") { Screen3(navController) }
        composable("screen4/{data}", arguments = listOf(navArgument("data") { type = NavType.StringType })) { backStackEntry ->
            Screen4(navController, backStackEntry.arguments?.getString("data") ?: "")
        }
    }
}

val bottomNavigationItems = listOf(
    NavigationItem("bottom_screen1", "Screen 1", Icons.Filled.Home), NavigationItem("bottom_screen2", "Screen 2", Icons.Filled.Settings)
)

val bottomBarRoutes = setOf("bottom_screen1", "bottom_screen2")

MainScreen.kt :

package com.simplifymindfulness.composebottom_navigationexample.composables

import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import com.simplifymindfulness.composebottom_navigationexample.AppNavigation
import com.simplifymindfulness.composebottom_navigationexample.bottomBarRoutes
import com.simplifymindfulness.composebottom_navigationexample.bottomNavigationItems


@Composable
fun MainScreen(navController: NavHostController) {

    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry?.destination?.route

    Scaffold(
        bottomBar = {
            if (currentRoute in bottomBarRoutes) {
                NavigationBar {
                    bottomNavigationItems.forEach { item ->
                        NavigationBarItem(
                            icon = { Icon(item.icon, contentDescription = null) },
                            label = { Text(item.label) },
                            selected = currentRoute == item.route,
                            onClick = {
                                if (currentRoute != item.route) {
                                    navController.navigate(item.route) {
                                        popUpTo(navController.graph.startDestinationId)
                                        launchSingleTop = true
                                    }
                                }
                            }
                        )
                    }
                }
            }
        }
    ) { innerPadding ->
        AppNavigation(navController, Modifier.padding(innerPadding))
    }
}

data class NavigationItem(val route: String, val label: String, val icon: ImageVector)

Screens.kt :

package com.simplifymindfulness.composebottom_navigationexample.composables


import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController

@Composable
fun Screen1(navController: NavController) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center
    ) {
        Text("This is Screen 1")

        Button(onClick = { navController.navigate("screen2") }) {
            Text("Go to Screen 2")
        }
    }
}

@Composable
fun Screen2(navController: NavController) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center
    ) {
        Text("This is Screen 2")

        // Explanation Text
        Text(
            "In this example, when navigating to Screen 3, " + "Screen 2 will be removed from the navigation stack. " + "This is achieved using popUpTo with inclusive = true."
        )

        Spacer(modifier = Modifier.height(16.dp))

        Button(onClick = {
            navController.navigate("screen3") {
                // This will remove Screen 2 from the stack
                popUpTo("screen2") { inclusive = true }
            }
        }) {
            Text("Go to Screen 3")
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Screen3(navController: NavController) {
    var text by remember { mutableStateOf("") }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center
    ) {
        OutlinedTextField(value = text, onValueChange = { text = it }, label = { Text("Enter something") })

        Spacer(modifier = Modifier.height(16.dp))

        Button(onClick = {
            navController.navigate("screen4/$text")
        }) {
            Text("Go to Screen 4 with data")
        }
    }
}

@Composable
fun Screen4(navController: NavController, data: String) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center
    ) {
        Text("Received data: $data")
    }
}

@Composable
fun BottomScreen1(navController: NavController) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp)
            .background(MaterialTheme.colorScheme.surface),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            "Welcome to Screen 1! Click the button below for nested navigation. The bottom navigation bar will be hidden.",
            style = MaterialTheme.typography.headlineSmall.copy(fontSize = 18.sp)
        )

        Button(onClick = { navController.navigate("screen1") }) {
            Text("Go to Screen 1")
        }
    }
}

@Composable
fun BottomScreen2(navController: NavController) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp)
            .background(MaterialTheme.colorScheme.background),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            "This is the Bottom Screen 2", style = MaterialTheme.typography.headlineSmall.copy(fontSize = 18.sp)
        )
    }
}

GitHub Repository

You can access the entire project and all related resources on GitHub to explore further or use it as a reference for your own implementations. Here’s the link to the repository:

GitHub - Jetpack Compose Bottom Navigation with Nested Navigation

Feel free to clone, fork, or star the repository for future reference, and check out how we've integrated all the components to create a smooth and user-friendly navigation experience in a Jetpack Compose app.

0
Subscribe to my newsletter

Read articles from saurabh jadhav directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

saurabh jadhav
saurabh jadhav