Skip to content

Instantly share code, notes, and snippets.

@nglauber
Created January 3, 2021 19:46
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nglauber/5061cbbafd63488131380b09c8943ad7 to your computer and use it in GitHub Desktop.
Save nglauber/5061cbbafd63488131380b09c8943ad7 to your computer and use it in GitHub Desktop.
Bottom Navigation + Jetpack Navigation
package com.example.samplecomposenavigation
import android.os.Bundle
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.List
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.*
import androidx.compose.runtime.savedinstancestate.Saver
import androidx.compose.runtime.savedinstancestate.rememberSavedInstanceState
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.core.os.bundleOf
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.samplecomposenavigation.ui.SampleComposeNavigationTheme
sealed class TabItem(val route: String, val title: String, val icon: ImageVector) {
object TabListInfo : TabItem(SCREEN_LIST, "Items", Icons.Filled.List)
object TabProfileInfo : TabItem(SCREEN_PROFILE, "Profile", Icons.Filled.Settings)
fun saveState(): Bundle {
return bundleOf(KEY_SCREEN to route)
}
companion object {
val defaultTab = TabListInfo
fun screenSaver(
): Saver<MutableState<TabItem>, *> = Saver(
save = { it.value.saveState() },
restore = { mutableStateOf(restoreState(it)) }
)
private fun restoreState(bundle: Bundle): TabItem {
return when (bundle.getString(KEY_SCREEN, defaultTab.route)) {
TabProfileInfo.route -> TabProfileInfo
TabListInfo.route -> TabListInfo
else -> defaultTab
}
}
const val KEY_SCREEN = "route"
}
}
@Composable
fun MainScreen() {
var currentTab by rememberSavedInstanceState(
saver = TabItem.screenSaver()
) { mutableStateOf(TabItem.defaultTab) }
val items = listOf(
TabItem.TabListInfo,
TabItem.TabProfileInfo,
)
SampleComposeNavigationTheme {
Scaffold(
bottomBar = {
BottomNavigation {
items.forEach { screen ->
BottomNavigationItem(
icon = { Icon(screen.icon) },
label = { Text(screen.title) },
selected = currentTab == screen,
onClick = {
currentTab = screen
}
)
}
}
}
) {
TabContent(currentTab)
}
}
}
@Composable
fun TabContent(tabItem: TabItem) {
val tab1NavState = rememberSavedInstanceState(saver = navStateSaver()) { mutableStateOf(Bundle()) }
val tab2NavState = rememberSavedInstanceState(saver = navStateSaver()) { mutableStateOf(Bundle()) }
when (tabItem) {
TabItem.TabListInfo -> {
TabWrapper(tab1NavState) {
TabList(it)
}
}
TabItem.TabProfileInfo -> {
TabWrapper(tab2NavState) {
TabProfile(it)
}
}
}
}
fun navStateSaver(): Saver<MutableState<Bundle>, out Any> = Saver(
save = { it.value },
restore = { mutableStateOf(it) }
)
@Composable
fun TabWrapper(
navState: MutableState<Bundle>,
body: @Composable (NavHostController) -> Unit
) {
val navController = rememberNavController()
onCommit {
val callback = NavController.OnDestinationChangedListener { navController, _, _ ->
navState.value = navController.saveState() ?: Bundle()
}
navController.addOnDestinationChangedListener(callback)
navController.restoreState(navState.value)
onDispose {
navController.removeOnDestinationChangedListener(callback)
// workaround for issue where back press is intercepted
// outside this tab, even after this Composable is disposed
navController.enableOnBackPressed(false)
}
}
body(navController)
}
@Composable
fun TabList(navController: NavHostController) {
NavHost(navController = navController, startDestination = SCREEN_LIST) {
composable(SCREEN_LIST) { ListScreen(navController) }
composable("$SCREEN_DETAILS/{$PARAM_USER_NAME}") { backStackEntry ->
DetailScreen(backStackEntry.arguments?.getString(PARAM_USER_NAME) ?: "")
}
}
}
@Composable
fun TabProfile(navController: NavHostController) {
NavHost(navController = navController, startDestination = SCREEN_PROFILE) {
composable(SCREEN_PROFILE) { ProfileScreen(navController) }
composable(SCREEN_PROFILE_DETAIL) { ProfileDetailScreen() }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment