Jetpack Compose is Google’s modern toolkit for building native Android UI. It simplifies UI development by using a declarative approach, making code more readable and maintainable. Navigation is a core component of any app, and Jetpack Compose provides a powerful navigation library to manage the app’s flow. Configuring a NavHost
is essential for setting up navigation within your Compose application.
What is NavHost
in Jetpack Compose?
The NavHost
is a Compose component that displays the current destination of a navigation graph. It links the navigation graph with the composable destinations, allowing users to navigate between different screens in your app. The NavHost
utilizes a NavController
to manage navigation actions.
Why Configure NavHost
?
- Centralized Navigation: Manages all destinations in one place.
- Transitions and Animations: Supports seamless transitions between screens.
- State Management: Integrates with
ViewModel
for state persistence across navigation events. - Deep Linking: Enables navigation to specific screens via deep links.
How to Configure NavHost
in Jetpack Compose
Follow these steps to set up a NavHost
in your Jetpack Compose application:
Step 1: Add Dependencies
Make sure you have the required dependencies in your build.gradle
file:
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
// Navigation Compose
implementation("androidx.navigation:navigation-compose:2.7.7")
}
Step 2: Define Navigation Destinations
Create sealed classes or objects representing your app’s navigation destinations:
sealed class Screen(val route: String) {
object Home : Screen("home")
object Details : Screen("details/{itemId}") {
fun createRoute(itemId: Int): String = "details/$itemId"
}
}
Step 3: Create a NavController
Initialize a NavController
using rememberNavController()
inside your composable function:
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
@Composable
fun MyApp() {
val navController: NavController = rememberNavController()
// ...
}
Step 4: Set up NavHost
Configure the NavHost
using the NavController
and a start destination. Define the composable content for each route within the NavHost
:
import androidx.compose.material3.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.NavType
import androidx.navigation.navArgument
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun MyApp() {
val navController: NavController = rememberNavController()
NavHost(navController = navController, startDestination = Screen.Home.route) {
composable(Screen.Home.route) {
HomeScreen(navController = navController)
}
composable(
route = Screen.Details.route,
arguments = listOf(navArgument("itemId") { type = NavType.IntType })
) { entry ->
val itemId = entry.arguments?.getInt("itemId")
itemId?.let {
DetailsScreen(itemId = it)
}
}
}
}
@Composable
fun HomeScreen(navController: NavController) {
androidx.compose.material3.Button(onClick = { navController.navigate(Screen.Details.createRoute(123)) }) {
Text("Go to Details")
}
}
@Composable
fun DetailsScreen(itemId: Int) {
Text("Details Screen for item ID: $itemId")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
MyApp()
}
In this example:
- We define two screens:
Home
andDetails
. - The
Details
screen includes a parameteritemId
. - The
NavHost
maps each route to a composable function. - We use
navArgument
to define the arguments passed to theDetails
screen.
Step 5: Navigate Between Screens
Use navController.navigate()
to navigate between different destinations:
// Navigate to the Details screen with itemId = 123
navController.navigate(Screen.Details.createRoute(123))
Passing Data Between Screens
You can pass data between screens by including arguments in the route definition:
// Define the route with a parameter
object Details : Screen("details/{itemId}") {
fun createRoute(itemId: Int): String = "details/$itemId"
}
// Navigate with the parameter
navController.navigate(Screen.Details.createRoute(123))
// Retrieve the parameter in the Details screen
composable(
route = Screen.Details.route,
arguments = listOf(navArgument("itemId") { type = NavType.IntType })
) { entry ->
val itemId = entry.arguments?.getInt("itemId")
// ...
}
Advanced Configuration
Animated Transitions
You can add animated transitions between screens using AnimatedContent
. To use AnimatedContent
you need to use androidx.compose.animation:animation-core
.
implementation("androidx.compose.animation:animation-core:1.6.1")
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
sealed class Screen(val route: String) {
object Home : Screen("home")
object Details : Screen("details")
}
@Composable
fun AnimatedNavHost() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screen.Home.route) {
composable(
Screen.Home.route,
enterTransition = {
slideInHorizontally(
initialOffsetX = { 300 },
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
) + fadeIn(animationSpec = tween(durationMillis = 300))
},
exitTransition = {
slideOutHorizontally(
targetOffsetX = { -300 },
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
) + fadeOut(animationSpec = tween(durationMillis = 300))
}
) {
HomeScreen(navController = navController)
}
composable(
Screen.Details.route,
enterTransition = {
slideInHorizontally(
initialOffsetX = { 300 },
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
) + fadeIn(animationSpec = tween(durationMillis = 300))
},
exitTransition = {
slideOutHorizontally(
targetOffsetX = { -300 },
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
) + fadeOut(animationSpec = tween(durationMillis = 300))
}
) {
DetailsScreen()
}
}
}
@Composable
fun HomeScreen(navController: NavController) {
Button(onClick = {
navController.navigate(Screen.Details.route)
}) {
Text(text = "Go to Details")
}
}
@Composable
fun DetailsScreen() {
Text(text = "Details Screen")
}
Navigation with Bottom Navigation
Implementing a bottom navigation bar can significantly improve user experience. Here’s how to configure it:
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.compose.ui.tooling.preview.Preview
sealed class BottomNavItem(val route: String, val icon: ImageVector, val label: String) {
object Home : BottomNavItem("home", Icons.Filled.Home, "Home")
object Settings : BottomNavItem("settings", Icons.Filled.Settings, "Settings")
}
@Composable
fun BottomNavigationApp() {
val navController = rememberNavController()
Scaffold(
bottomBar = { BottomNavigationBar(navController = navController) }
) {
NavigationGraph(navController = navController)
}
}
@Composable
fun BottomNavigationBar(navController: NavController) {
val items = listOf(
BottomNavItem.Home,
BottomNavItem.Settings
)
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
BottomNavigationItem(
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(text = item.label) },
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
navController.graph?.startDestinationRoute?.let { route ->
popUpTo(route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
@Composable
fun NavigationGraph(navController: NavController) {
NavHost(navController = navController, startDestination = BottomNavItem.Home.route) {
composable(BottomNavItem.Home.route) {
HomeScreen(navController = navController)
}
composable(BottomNavItem.Settings.route) {
SettingsScreen()
}
}
}
@Composable
fun HomeScreen(navController: NavController) {
Text(text = "Home Screen")
}
@Composable
fun SettingsScreen() {
Text(text = "Settings Screen")
}
@Preview(showBackground = true)
@Composable
fun BottomNavigationAppPreview() {
BottomNavigationApp()
}
Conclusion
Configuring a NavHost
in Jetpack Compose is crucial for building robust and navigable Android applications. By defining destinations, setting up the NavController
, and using navigation actions, you can create seamless transitions and manage your app’s flow efficiently. Additionally, advanced features such as animated transitions and bottom navigation can enhance the user experience and make your app more engaging.