The Code A is from the end branch of the official sample project.
When I click the Tab of the UI, the App will show the corresponding UI, and the current Tab will be marked as a different style.
In Overview.name Tab UI page, if I click a account item, the code composable(Overview.name) {... onAccountClick = { name -> navigateToSingleAccount(navController, name) } will navigate it to composable( route = "$accountsName/{name}", ..) UI , and Accounts.name is marked as current Tab.
What make me confuse is why Android can make Accounts.name as current Tab, could you tell me? Will navController.navigate() cause Android recomposition? so is val currentScreen = RallyScreen.fromRoute(backstackEntry.value?.destination?.route) re-executed ?
Code A
#Composable
fun RallyApp() {
RallyTheme {
val allScreens = RallyScreen.values().toList()
val navController = rememberNavController()
val backstackEntry = navController.currentBackStackEntryAsState()
val currentScreen = RallyScreen.fromRoute(backstackEntry.value?.destination?.route)
Scaffold(
topBar = {
RallyTabRow(
allScreens = allScreens,
onTabSelected = { screen ->
navController.navigate(screen.name)
},
currentScreen = currentScreen
)
}
) { innerPadding ->
RallyNavHost(navController, modifier = Modifier.padding(innerPadding))
}
}
}
#Composable
fun RallyNavHost(navController: NavHostController, modifier: Modifier = Modifier) {
NavHost(
navController = navController,
startDestination = Overview.name,
modifier = modifier
) {
composable(Overview.name) {
OverviewBody(
onClickSeeAllAccounts = { navController.navigate(Accounts.name) },
onClickSeeAllBills = { navController.navigate(Bills.name) },
onAccountClick = { name ->
navigateToSingleAccount(navController, name)
},
)
}
composable(Accounts.name) {
AccountsBody(accounts = UserData.accounts) { name ->
navigateToSingleAccount(navController = navController, accountName = name)
}
}
composable(Bills.name) {
...
}
val accountsName = Accounts.name
composable(
route = "$accountsName/{name}",
arguments = listOf(
navArgument("name") {
type = NavType.StringType
}
),
deepLinks = listOf(
navDeepLink {
uriPattern = "rally://$accountsName/{name}"
}
),
) { entry ->
val accountName = entry.arguments?.getString("name")
val account = UserData.getAccount(accountName)
SingleAccountBody(account = account)
}
}
}
private fun navigateToSingleAccount(navController: NavHostController, accountName: String) {
navController.navigate("${Accounts.name}/$accountName")
}
enum class RallyScreen(
val icon: ImageVector,
) {
Overview(
icon = Icons.Filled.PieChart,
),
Accounts(
icon = Icons.Filled.AttachMoney,
),
Bills(
icon = Icons.Filled.MoneyOff,
);
companion object {
fun fromRoute(route: String?): RallyScreen =
when (route?.substringBefore("/")) {
Accounts.name -> Accounts
Bills.name -> Bills
Overview.name -> Overview
null -> Overview
else -> throw IllegalArgumentException("Route $route is not recognized.")
}
}
}
Yes, navigate() will definitely cause recomposition as the UI is getting re-created.
Yes, val currentScreen = RallyScreen.fromRoute(backstackEntry.value?.destination?.route) will be re-executed as backstackEntry is being observed as state and its value will change when you navigate. That's why the RallyApp composable will also get recomposed. So when it is executed again, currentScreen value will get updated.
You can solve it using Effects as per your requirement. In this scenario, you can use LaunchedEffect. like,
LaunchedEffect(null) {
// code that you don't want to run again.
}
Another approach for now(Which I do not recommend), to avoid your composable code from re-running by checking the current route in the back-stack entry and your expected route. To do that you can use the following piece of code.
#Composable
fun YourComposableScreen(navController: NavController) {
if (yourExpectedRoute != navController.currentBackStackEntry?.destination?.route) {
return
}
// Your other composable(s) goes here.
}
Related
I have BottomNavigation wrapped around as a BottomBar composable and inside my MainScreen composable.
The current implementation renders properly and I am able to navigate between different Pages. However, the issue I am facing is that the selected property in the BottomNavigationItem is not been updated correctly when changing the current page. This causes the UI to be located in the "Profile" page, but the bottom bar highlights the "Home" page.
I am almost sure it is something regarding the state of the composable, and I tried passing the NavHostController all the way down to the BottomBar, but that didn't solved the issue.
enum class Page(
#StringRes val title: Int,
val isCorePage: Boolean,
val icon: ImageVector) {
Profile(
title = R.string.profile_page_title,
isCorePage = true,
icon = Icons.Filled.Person),
Home(
title = R.string.home_page_title,
isCorePage = true,
icon = Icons.Filled.Home),
... etc
}
#Composable
fun BottomBar(
currentPage: Page,
onClick: (page: Page) -> Unit = { }) {
BottomNavigation {
Page.values().forEach { page ->
if(page.isCorePage) {
val title = stringResource(page.title);
BottomNavigationItem(
icon = { Icon(
imageVector = page.icon,
contentDescription = title) },
label = { Text(text = title) },
selected = page == currentPage,
onClick = { onClick(page) }
)
}
}
}
}
#Composable
fun MainScreen(
sessionState: State<Session>,
navController: NavHostController = rememberNavController()) {
val backStackEntry by navController.currentBackStackEntryAsState()
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopBar(
title = backStackEntry?.destination?.route ?: "Title",
menuClick = { scope.launch { scaffoldState.drawerState.open() } },
searchClick = { navController.navigate(Page.Search) },
notificationsClick = { navController.navigate(Page.Notifications) })
},
drawerContent = { SideDrawer(sessionState = sessionState) },
bottomBar = {
BottomBar(
currentPage = navController.currentPageOrDefault(Page.Home),
onClick = { page -> navController.navigate(page) })
}
) {
PageNavHost(navController)
}
}
fun NavHostController.navigate(page: Page) {
val navController = this
navController.navigate(page.name) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// re-selecting the same item
launchSingleTop = true
// Restore state when re-selecting a previously selected item
restoreState = true
}
}
fun NavHostController.currentPageOrDefault(default: Page): Page {
return this.currentPage() ?: default
}
fun NavHostController.currentPage(): Page? {
val currentRoute = this.currentDestination?.route ?: return null;
Page.values().forEach { page ->
if(page.name == currentRoute) {
return page
}
}
return null
}
I want to implement a simple user flow, where the user sees multiple screens to input data. The flow should share a common navbar where each screen can contribute its menu items to when it is active (e.g. add a "search" or a "next" button). The navbar also has buttons belonging conceptually to the user flow and not to individual screens (like the back button and a close button). Screens should be reusable in other contexts, so screens should not know about the flow they operate in.
Technically the user flow is implemented as a compose function defining the navbar and using compose navigation. Each screen is implemented as a separate compose function.
In fragment/view based Android this scenario was supported out of box with onCreateOptionsMenu and related functions. But how would I do this in compose? I could not find any guidance on that topic.
To illustrate the problem in code:
#Composable
fun PaymentCoordinator(
navController: NavHostController = rememberNavController()
) {
AppTheme {
Scaffold(
bottomBar = {
BottomAppBar(backgroundColor = Color.Red) {
IconButton(onClick = navController::popBackStack) {
Icon(Icons.Filled.ArrowBack, "Back")
}
Spacer(modifier = Modifier.weight(1f))
// 0..n IconButtons provided by the active Screen
// should be inserted here
// How can we do that, because state should never
// go up from child to parent
// this button (or at least its text and onClick action) should
// be defined by the currently visible Screen as well
Button(
onClick = { /* How to call function of screen? */ }
) {
Text("Next"))
}
}
}
) { padding ->
Box(modifier = Modifier.padding(padding)) {
NavHost(
navController = navController,
startDestination = "selectAccount"
) {
// screens that can contribute items to the menu
composable("selectAccount") {
AccountSelectionRoute(
onAccountSelected = {
navController.navigate("nextScreen")
}
)
}
composable("...") {
// ...
}
}
}
}
}
}
I came up with an approach leveraging side effects and lifecycle listener to achieve my goal. Basically whenever a screen becomes active (ON_START) it informs the parent (coordinator) about its menu configuration. The coordinator evaluates the configuration and updates the navbar accordingly.
The approach is based on Googles documentation on side effects (https://developer.android.com/jetpack/compose/side-effects#disposableeffect)
The approach feels complicated and awkward and I think the compose framework is missing some functionality to achieve this here. However, my implementation seems to be working fine in my test use case.
Helper classes
// currently I only need to configure a single button, however the approach
// can be easily extended now (you can put anything inside rightButton)
data class MenuConfiguration(
val rightButton: #Composable () -> Unit
)
#Composable
fun SimpleMenuConfiguration(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onRegisterMenuConfiguration: (MenuConfiguration?) -> Unit,
onUnregisterMenuConfiguration: () -> Unit,
rightButton: #Composable () -> Unit
) {
val currentOnRegisterMenuConfiguration by rememberUpdatedState(onRegisterMenuConfiguration)
val currentOnUnregisterMenuConfiguration by rememberUpdatedState(onUnregisterMenuConfiguration)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
currentOnRegisterMenuConfiguration(
MenuConfiguration(
rightButton = rightButton
)
)
} else if (event == Lifecycle.Event.ON_STOP) {
currentOnUnregisterMenuConfiguration()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
Coordinator level
#Composable
fun PaymentCoordinator(
navController: NavHostController = rememberNavController()
) {
var menuConfiguration by remember { mutableStateOf<MenuConfiguration?>(null) }
AppTheme {
Scaffold(
bottomBar = {
BottomAppBar(backgroundColor = Color.Red) {
IconButton(onClick = navController::popBackStack) {
Icon(Icons.Filled.ArrowBack, "Back")
}
Spacer(modifier = Modifier.weight(1f))
menuConfiguration?.rightButton?.invoke()
}
}
) { padding ->
Box(modifier = Modifier.padding(padding)) {
PaymentNavHost(
navController = navController,
finishedHandler = finishedHandler,
onRegisterMenuConfiguration = { menuConfiguration = it },
onUnregisterMenuConfiguration = { menuConfiguration = null }
)
}
}
}
}
#Composable
fun PaymentNavHost(
navController: NavHostController = rememberNavController(),
onRegisterMenuConfiguration: (MenuConfiguration?) -> Unit,
onUnregisterMenuConfiguration:() -> Unit
) {
NavHost(
navController = navController,
startDestination = "selectAccount"
) {
composable("selectAccount") {
DemoAccountSelectionRoute(
onAccountSelected = {
navController.navigate("amountInput")
},
onRegisterMenuConfiguration = onRegisterMenuConfiguration,
onUnregisterMenuConfiguration = onUnregisterMenuConfiguration
)
}
composable("amountInput") {
AmountInputRoute(
onRegisterMenuConfiguration = onRegisterMenuConfiguration,
onUnregisterMenuConfiguration = onUnregisterMenuConfiguration,
onFinished = {
...
}
)
}
}
}
Screen level
#Composable
internal fun AmountInputRoute(
onRegisterMenuConfiguration: (MenuConfiguration?) -> Unit,
onUnregisterMenuConfiguration:() -> Unit,
onFinished: (Amount?) -> Unit
) {
SimpleMenuConfiguration(
onRegisterMenuConfiguration = onRegisterMenuConfiguration,
onUnregisterMenuConfiguration = onUnregisterMenuConfiguration,
rightButton = {
Button(
onClick = {
...
}
) {
Text(text = stringResource(id = R.string.next))
}
}
)
I am migrating my multiple activity app to single activity app for compose.
I have created a composable Home which contains a Top app bar with a title as shown below:
#Composable
fun Home() {
val navController = rememberNavController()
var actionBarTitle by rememberSaveable { mutableStateOf("Home") }
var actionBarSubtitle by rememberSaveable { mutableStateOf("") }
Scaffold(topBar = {
Header(title = actionBarTitle, subTitle = actionBarSubtitle,
onBackPress = { navController.popBackStack() },
showInfo = true, onActionClick = {
navController.navigate(Screen.Info.route)
}, modifier = Modifier.fillMaxWidth())
}) {
AppNavigation(navController = navController, onNavigate = { title, subtitle ->
actionBarTitle = title
actionBarSubtitle = subtitle
})
}
onNavigate is triggered whenever I use navController.navigate for any screen as shown below:
onNavigate("Top up", "Please topm up with minimum of X amount")
navController.navigateTo(Screen.TopUp.route)
My question is when I use backpress I don't know to which screen composable I will be navigated to, so how can I call onNavigate to change the title.
You can observe the navigation changes using the currentBackstackEntryFlow.
#Composable
fun Home() {
val context = LocalContext.current
val navController = rememberNavController()
...
LaunchedEffect(navController) {
navController.currentBackStackEntryFlow.collect { backStackEntry ->
// You can map the title based on the route using:
actionBarTitle = getTitleByRoute(context, backStackEntry.destination.route)
}
}
...
}
Of course, you would need write this getTitleByRoute() to get the correct title in according to the navigation route.
It would be something like:
fun getTitleByRoute(context: Context, route:String): String {
return when (route) {
"Screen1" -> context.getString(R.string.title_screen_1)
// other cases
else -> context.getString(R.string.title_home)
}
}
1. Use LiveData to change the Screen Title while using Composable
implementation "androidx.compose.runtime:runtime-livedata:1.2.0-beta02"
2. Create ViewModel Class
class MainViewModel: ViewModel() {
private var _screenTitle = MutableLiveData("")
val screenTitle: LiveData<String>
get() = _screenTitle
fun setTitle(newTitle: String) {
_screenTitle.value = newTitle
}
}
3. In Your Activity Class
setContent {
Surface(color = MaterialTheme.colors.onPrimary) {
LoadMainScreen()
}
}
// Observe ScreenTitle
#Composable
fun LoadMainScreen(mainViewModel: MainViewModel = viewModel()){
val title: String by mainViewModel.screenTitle.observeAsState("")
Scaffold(
topBar = {
TopAppBar(title = { title?.let { Text(it) } },
navigationIcon = {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Menu",
tint = Color.White
)
}
)
}
)
}
4. Change the Title Value from Screen
#Composable
fun ScreenOne(mainViewModel: MainViewModel) {
LaunchedEffect(Unit){
mainViewModel.setTitle("One")
}
}
#Composable
fun ScreenTwo(mainViewModel: MainViewModel) {
LaunchedEffect(Unit){
mainViewModel.setTitle("Two")
}
}
You can get the label of the current destination from navHostcontrollor, just use it as the title
val navController = rememberNavController()
val currentBackStackEntry by navController.currentBackStackEntryAsState()
val title = currentBackStackEntry?.destination?.label
The default composable function is implemented as follows
/**
* Add the [Composable] to the [NavGraphBuilder]
*
* #param route route for the destination
* #param arguments list of arguments to associate with destination
* #param deepLinks list of deep links to associate with the destinations
* #param content composable for the destination
*/
public fun NavGraphBuilder.composable(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
content: #Composable (NavBackStackEntry) -> Unit
) {
addDestination(
ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
this.route = route
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
deepLinks.forEach { deepLink ->
addDeepLink(deepLink)
}
}
)
}
overload it:
fun NavGraphBuilder.composable(
route: String,
label: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
content: #Composable (NavBackStackEntry) -> Unit
) {
addDestination(
ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
this.route = route
this.label = label
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
deepLinks.forEach { deepLink ->
addDeepLink(deepLink)
}
}
)
}
You can use it this way:
composable("route", "title") {
...
}
I was developing an App with Jetpack Compose, and I try to implement the Navigation Compose component.
My use of case is based on 3 Screens without fragments:
List Screen
Detail Screen
Bought Screen
I also implement a BottomTab where I have List Screen and Bought Screen. the Detail Screen is access from List Screen.
BottomNavigation.kt
#Composable
fun BottomNavigationBar(heightBottomBar: Int, navController: NavController) {
Log.d("navigation", "navigatorName: ${navController.currentBackStackEntry?.destination?.navigatorName}")
Log.d("navigation", "displayName: ${navController.currentBackStackEntry?.destination?.displayName}")
Log.d("navigation", "arguments: ${navController.currentBackStackEntry?.arguments}")
val items = listOf(
BottomNavItem.ListScreen,
BottomNavItem.BoughtScreen
)
BottomNavigation(
Modifier.height(heightBottomBar.dp),
backgroundColor = MaterialTheme.colors.background,
contentColor = Color.White)
{
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
BottomNavigationItem(
icon = { Icon(painterResource(id = item.icon), contentDescription = item.title) },
label = {
Text(
text = item.title,
fontSize = 9.sp
)
},
selectedContentColor = androidx.compose.ui.graphics.Color.Black,
unselectedContentColor = androidx.compose.ui.graphics.Color.Black.copy(0.4f),
alwaysShowLabel = true,
selected = currentRoute == item.screen_route,
onClick = {
//Added to manage navigate to List screen, instead of detail screen when user pop List tab from BoughtScreen.
// if(item.screen_route == "pokemon_list_screen" && navBackStackEntry?.destination?.parent?.route == "pokemon_list_screen"){
// navController.popBackStack()
// }else{
navController.navigate(item.screen_route) {
navController.graph.startDestinationRoute?.let { screen_route ->
popUpTo(screen_route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
}
//}
)
}
}
}
And ButtonNav.kt file
sealed class BottomNavItem(var title:String, var icon:Int, var screen_route:String){
object BoughtScreen : BottomNavItem("My Pokemons", R.drawable.ic_all_inbox,"pokemon_bought_screen")
object ListScreen: BottomNavItem("List",R.drawable.ic_list,"pokemon_list_screen")
}
the issue is when I try to navigate from Bought Screen to List screen, pop in the List tab, which take me to detail screen, when it should take to List Screen.
The navigation implementation is the following:
#Composable
fun NavigationGraph(navController: NavHostController) {
NavHost(navController = navController, startDestination = Screens.List.route) {
composable(Screens.List.route) {
PokemonListScreen(navController = navController)
}
composable(
Screens.Detail.route,
// use as URL
//which pokemon you want to see in detail with colour in background pass arguments
arguments = listOf(
navArgument("dominantColor") {
type = NavType.IntType
},
navArgument("pokemonName") {
type = NavType.StringType
},
navArgument("number") {
type = NavType.IntType
}
),
) {
val dominantColor = remember {
val color = it.arguments?.getInt("dominantColor")
color?.let { Color(it) } ?: Color.White
}
val pokemonName = remember {
it.arguments?.getString("pokemonName")
}
val pokemonNumber = remember {
it.arguments?.getInt("number")
}
if (pokemonName != null) {
PokemonDetailScreen(
activity = this#MainActivity,
context = applicationContext,
dominantColor = dominantColor,
pokemonName = pokemonName.toLowerCase(Locale.ROOT) ?: " ",
pokemonNumber = pokemonNumber!!,
navController = navController
)
}
}
composable(Screens.Bought.route) {
MyPokemonsScreen(
context = applicationContext,
dominantColor = MaterialTheme.colors.background,
navController = navController
)
}
}
}
And my Screens.kt file:
sealed class Screens(val route: String) {
object List : Screens("pokemon_list_screen")
object Bought : Screens("pokemon_bought_screen")
object Detail : Screens("pokemon_detail_screen/{dominantColor}/{pokemonName}/{number}")
}
As I comment, with this implementation the apps works fine, except when I navigate to Bought Screen, and I click in the List Tab, the app take me to Detail Screen.
So my question is: Should I modify NavController to remove DetailScreen from currentBackStackEntry, or I should manage this from Bottomavigation::class.
I just start with Jetpack Compose, and I a bit undevelop on this topics.
If you have some knowledge about Jetpack Compose Navigation, and are able to help, take thanks in advance !
How to show navigation icon (BackArrow or Menu) in TopAppBar using Scaffold based on actual position in NavController? I am using Navigating with Compose 1.0.0-alpha02. Below is a sample code with a description of how it should work
#Composable
fun App()
{
val navController = rememberNavController()
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "App title") },
navigationIcon = {
/*
Check if navController back stack has more
than one element. If so show BackButton.
Clicking on that button will move back
*/
val canMoveBack = true
if (canMoveBack)
{
IconButton(onClick = {
// Move back
navController.popBackStack()
}) {
Icon(asset = Icons.Outlined.ArrowBack)
}
}
else
{
IconButton(onClick = {
// show NavDrawer
}) {
Icon(asset = Icons.Outlined.Menu)
}
}
},
)
},
bodyContent = {
AppBody(navController)
}
)
}
I thought about something like navController.backStack.size but I got error NavController.getBackStack can only be called from within the same library group (groupId=androidx.navigation).
And the second question, if I wanted to change the TopAppBar text do I have to hoist this text and give every "screen" possibility to change this text, or is there any easy built-in way to do this like in the standard View System?
Thanks to Abdelilah El Aissaoui I have got an idea of how to do it with one Scaffold and just changing bodyContent. In this method, we don't have to pass navController to any body element, everything is done in base App composable. Below is code which enables to navigate between two bodies (Lesson -> Student)
App:
#Composable
fun App(
viewModel: MainViewModel
)
{
val navController = rememberNavController()
val baseTitle = "" // stringResource(id = R.string.app_name)
val (title, setTitle) = remember { mutableStateOf(baseTitle) }
val (canPop, setCanPop) = remember { mutableStateOf(false) }
val scaffoldState: ScaffoldState = rememberScaffoldState()
navController.addOnDestinationChangedListener { controller, _, _ ->
setCanPop(controller.previousBackStackEntry != null)
}
// check navigation state and navigate
if (viewModel.navigateToStudents.value)
{
navController.navigate(route = STUDENT_SCREEN_ROUTE)
viewModel.studentsNavigated()
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = title) },
navigationIcon = {
if (canPop)
{
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(asset = Icons.Outlined.ArrowBack)
}
}
else
{
IconButton(onClick = {
scaffoldState.drawerState.open()
}) {
Icon(asset = Icons.Outlined.Menu)
}
}
},
)
},
scaffoldState = scaffoldState,
drawerContent = {
DrawerContent()
},
bodyContent = {
AppBody(
viewModel = viewModel,
navController = navController,
setTitle = setTitle
)
}
)
}
AppBody
#Composable
fun AppBody(
viewModel: MainViewModel,
navController: NavHostController,
setTitle: (String) -> Unit,
)
{
NavHost(
navController,
startDestination = LESSON_SCREEN_ROUTE
) {
composable(route = LESSON_SCREEN_ROUTE) {
LessonBody(
viewModel = viewModel,
setTitle = setTitle
)
}
composable(
route = STUDENT_SCREEN_ROUTE
) {
StudentBody(
viewModel = viewModel,
setTitle = setTitle
)
}
}
}
In the ViewModel I use this pattern to navigate:
private val _navigateToStudents: MutableState<Boolean> = mutableStateOf(false)
val navigateToStudents: State<Boolean> = _navigateToStudents
fun studentsNavigated()
{
// here we can add any logic after doing navigation
_navigateToStudents.value = false
}
So when I want to navigate to the next fragment I just set _navigateToStudents.value = true
I was just trying to achieve exactly the same today. I think this code will answer both questions:
#Composable
fun NavigationScaffold(
title: String? = null,
navController: NavController? = null,
bodyContent: #Composable (PaddingValues) -> Unit
) {
val navigationIcon: (#Composable () -> Unit)? =
if (navController?.previousBackStackEntry != null) {
{
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(Icons.Filled.ArrowBack)
}
}
} else {
// this can be null or another component
// If null, the navigationIcon won't be rendered at all
null
}
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = title.orEmpty())
},
navigationIcon = navigationIcon,
)
},
bodyContent = bodyContent
)
}
As you can see, you can provide a title as a String so you don't have to worry about passing a Text composable.
This Scaffold is the base of all my screens and to use it a simply have to write something like that:
#Composable
fun Home(navController: NavController) {
NavigationScaffold(
title = "Home",
navController = navController
) {
// Screen's content...
}
}