I'm running into a problem when trying to navigate with argument in my very first compose project
Error:
java.lang.IllegalArgumentException: Navigation destination that matches request NavDeepLinkRequest{ uri=android-app://androidx.navigation/transaction_detail/{1} } cannot be found in the navigation graph NavGraph...
My NavGraph:
#Composable
fun SetupNavGraph(
navController: NavHostController
) {
NavHost(
navController = navController,
startDestination = HomeDestination.route,
) {
composable(route = HomeDestination.route) {
HomeScreen(
navigateToItemEntry = { navController.navigate(TransactionEntryDestination.route) },
navigateToItemUpdate = {
navController.navigate("${TransactionDetailDestination.route}/{$it}")
}
)
}
//detail screen route
composable(
route = TransactionDetailDestination.routeWithArgs,
arguments = listOf(
navArgument(TransactionDetailDestination.transactionIdArg) {
type = NavType.IntType
}
)
) {
val id = it.arguments?.getInt(TransactionDetailDestination.transactionIdArg)!!
TransactionDetailScreen(id)
}
}
}
My transaction detail screen:
object TransactionDetailDestination : NavigationDestination {
override val route = "transaction_detail"
override val title = "Transaction Detail Screen"
const val transactionIdArg = "transactionId"
val routeWithArgs = "$route/{$transactionIdArg}"
}
#Composable
fun TransactionDetailScreen(id: Int) {
Scaffold {
TransactionDetailBody(paddingValues = it, id = id)
}
}
#Composable
fun TransactionDetailBody(
paddingValues: PaddingValues,
id: Int
) {
Column(modifier = Modifier.fillMaxSize()) {
Text(text = "$id", fontSize = 100.sp)
...
}
}
I can see that the problem is the route to transaction detail destination, but I don't know where to correct. I'm looking forward to every suggestion!
By research on internet a lot a realize that when specify the route to go, in my case, always like this:
//'it' is the argument we need to send
//rule: 'route/value1/value2...' where 'value' is what we trying to send over
navController.navigate("${TransactionDetailDestination.route}/$it")
The string of the route we need to extract the argument(s) from:
//notice the naming rule: 'route/{arg1}/{arg2}/...'
val routeWithArgs = "${route}/{${transactionIdArg}}"
Only be doing the above the compiler will understand the argument you are trying to send and receive. My mistake not reading carefully. Hope it helps!
I think you didn't declare your destination argument in your graph like this
composable("transaction_detail/{id}")
according to this documentation
Related
What is the correct way of passing arguments to startDestination of a nested navigation graph? See this example:
private const val featureGraphRoute = "feature_graph"
private const val firstRouteArg = "intArgument"
private const val firstRoute = "first_route/{$firstRouteArg}"
private const val secondRoute = "second_route"
fun NavController.navigateToFeatureGraph(argument:Int, navOptions: NavOptions? = null) {
//TODO: pass the argument
this.navigate(featureGraphRoute, navOptions)
}
fun NavGraphBuilder.featureGraph() {
navigation(
route = featureGraphRoute,
startDestination = firstRoute
) {
composable(
route = firstRoute,
arguments = listOf(
navArgument(firstRouteArg){
type = NavType.IntType
}
)
) { backStackEntry ->
FirstRoute(
argument = backStackEntry.arguments?.getInt(firstRouteArg)
)
}
composable(route = firstRoute) {
SecondRoute()
}
}
}
Adding the same argument to the featureGraphRoute does seem to work but only if using NavType.StringType. Otherwise app crashes with exception:
java.lang.IllegalArgumentException: Wrong argument type for 'intArgument' in argument bundle. integer expected.
EDIT:
I somehow missed the fact that NavGraphBuilder.navigation has an overload that takes arguments. Moving the navArgument declaration up one level does prevent the crash.
I am working on Jetpack Compose Navigation demo and I have a nested navigation graph with two different nested routes and screens for each nested route:
Login Graph
Main Graph
Login Graph has three routes for display three different Screens
Route "login" for displaying LoginScreen
Route "register" for displaying RegisterScreen
Route "recoverPassword" for displaying RecoverPasswordScreen
Main Graph has two routes for these screens
Route "home" for displaying HomeScreen
Route "settings" for displaying SettingsScreen
The nested graph creation is called in the MainActivity.kt
setContent {
NavigationDemoTheme {
val navController = rememberNavController()
SetupNavGraph(navController = navController)
}
}
The function in the file NestedNavGraph.kt looks like this:
fun SetupNavGraph(navController: NavHostController) {
NavHost(navController = navController, startDestination = "login_route")
{
loginGraph(navController = navController)
mainGraph(navController = navController)
}
}
In the file LoginNavGraph.kt I have defined the routes and start destination
fun NavGraphBuilder.loginGraph(navController: NavController) {
navigation(startDestination = "login", route = "login_route") {
composable(route = "login") {
LoginScreen(navController = navController)
}
composable(route = "register") {
RegisterScreen(navController = navController)
}
composable(route = "recover") {
RecoverPasswordScreen(navController = navController)
}
}
}
In the file MainNavGraph.kt I have defined these two routes and this start destination:
navigation(startDestination = "home", route = "main_route") {
composable(route = "home") {
HomeScreen(navController = navController)
}
composable(route = "settings") {
SettingsScreen(navController = navController)
}
}
My questions now is: How can I display the RecoverPasswordScreen from SettingsScreen. I know I can navigate to the "login_route" from the SettingsScreen with but then the startDestination will be displayed, which is the LoginScreen.
// shows the LoginScreen because the startDestination in the "login_route" is set to "login"
navController.navigate(route = "login_route")
So, how can I directly navigate to the route "recover" in the nested graph route "login_route"? The following "workarounds" are in my mind:
Pass a parameter to the "login_route", for example something with:
navController.navigate(route = "login_route?destination=recover")
I will then have only a single route as a destination, for example "LoginView". This will change the loginGraph like this:
fun NavGraphBuilder.loginGraph(navController: NavController) {
navigation(startDestination = "login_view, route = "login_route/{destination}) {
composable(
route = "login_view",
arguments = listOf(
navArgument("destination") { defaultValue = "login" },
)
) { backStackEntry ->
val destination = backStackEntry.arguments?.getString("destination");
destination?.let { destination ->
LoginView(destination = destination)
}
}
}
}
The LoginView is composable whichw will have a own NavHost where I can set the startDestination with the query parameter from the previous route:
fun LoginView( destination : String = "login"){
val navController = rememberNavController()
var startDestination = destination;
Scaffold ()
{
NavHost(
navController = navController,
startDestination = startDestination
) {
composable(route = "login") {
LoginScreen(navController = navController)
}
composable(route = "register") {
RegisterScreen(navController = navController)
}
composable(route = "recover") {
RecoverPasswordScreen(navController = navController)
}
}
}
Now I should be able to call the RecoverPasswordScreen from the SettingsScreen with this:
navController.navigate(route = "login_route?destination=recover")
Another possibility is to have extra route for the RecoverPassword Screen in the MainGraph defined. Is there any other possibilty to directly acess a route in a nested graph? It would be great if could dynamically change startDestination when routing to "login_route" but I don't know how or if this is even possible.
Compose allows you to (Navigate with arguments). This allows you to navigate to what you are calling "nested routes", that is a specific part within a screen.
Now, this is a simple explanation and I could leave you and have you figure it out. But I don't think this would be helpful to you as I think you have implemented your navigation in a difficult manner. Hence why trying to navigate is a bit more complex.
Here is a better way to implement it so that navigation like the one you want(RecoverPasswordScreen from Settings Screen) is easier.
Disclaimers
Change anything that's referred to as Main to your AppName.
I have not added all your screens
Main Screen class
//you could pass in parameters if needed into this constructor
enum class MainScreen(){
//these are your screens
LogIn(),
Settings(),
Recover(),
Home();
companion object {
fun fromRoute(route: String?): MainScreen =
when (route?.substringBefore("/")) {
LogIn.name -> LogIn
Home.name -> Home
Settings.name -> Settings
Recover.name -> Recover
//add the remaining screens
// a null route resolves to LogInScreen.
null -> LogIn
else -> throw IllegalArgumentException("Route $route is not recognized.")
}
}
}
Main Activity Class
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainApp()
}
}
}
#Composable
fun MainApp() {
MainTheme {
val allScreens = MainScreen.values().toList()
val navController = rememberNavController()
val backStackEntry = navController.currentBackStackEntryAsState()
// currentScrren user is on good if app is large
val currentScreen = MainScreen.fromRoute(
backStackEntry.value?.destination?.route
)
//Using scaffold is a good idea
Scaffold(
//add topAppBar and all other things here
) { innerPadding ->
MainNavHost(navController = navController, modifier = Modifier.padding(innerPadding))
}
}
}
//Scaffold requires innerPadding so remove if you decide not to use scaffold
#Composable
fun MainNavHost(navController: NavHostController, modifier: Modifier = Modifier) {
NavHost(
navController = navController,
startDestination = LogIn.name,
modifier = modifier
) {
composable(LogIn.name) {
/**
Your body for logIn page
**/
}
//this is how you will navigate to Recover Screen from settings
composable(Settings.name) {
SettingsBody(onClickRecoverScreen = {navController.navigate(Recover.name)})
}
}
composable(Recover.name) {
/**
Your body for Recover page
**/
}
composable(Home.name) {
/**
Your body for Home page
**/
}
}
Settings Screen
#Composable
fun SettingsBody(
//this callback is how you will navigate from Settings to RecoverPassword
onClickRecoverScreen: () -> Unit = {},
) {
Column(
//Add your designs for this screen
) {
Button(onClick = {onClickRecoverScreen})
}
}
This is the simplest way (in my opinion) to implement Navigation as you can simply add callbacks to navigate to different places in the app and it is much more testable(if you test ;) ) and scalable. You can also add deep links and use arguments (as mentioned above) to navigate to specific parts of the app (e.g., a specific account in an Accounts Screen)
I highly recommend this Navigation Codelab if you want to understand more.
A possible solution is to use deeplinks defined in the navigation graph - they also work for nested destinations. Then, instead of navigating to the route name, you can use navController.navigate(deepLinkUri)
I have an Android Jetpack Compose application that uses BottomNavigation and TopAppBar composables. From the tab opened via BottomNavigation users can navigate deeper into the navigation graph.
The problem
The TopAppBar composable must represent the current screen, e.g. display its name, implement some options that are specific to the screen opened, the back button if the screen is high-level. However, Jetpack Compose seems to have no out-of-the-box solution to that, and developers must implement it by themselves.
So, obvious ideas come with obvious drawbacks, some ideas are better than others.
The baseline for tracking navigation, as suggested by Google (at least for BottomNavigation), is a sealed class containing objects that represent the current active screen. Specifically for my project, it's like this:
sealed class AppTab(val route: String, #StringRes val resourceId: Int, val icon: ImageVector) {
object Events: AppTab("events_tab", R.string.events, Icons.Default.EventNote)
object Projects: AppTab("projects_tab", R.string.projects, Icons.Default.Widgets)
object Devices: AppTab("devices_tab", R.string.devices, Icons.Default.DevicesOther)
object Employees: AppTab("employees_tab", R.string.employees, Icons.Default.People)
object Profile: AppTab("profile_tab", R.string.profile, Icons.Default.AccountCircle)
}
Now the TopAppBar can know what tab is opened, provided we remember the AppTab object, but how does it know if a screen is opened from within a given tab?
Solution 1 - obvious and obviously wrong
We provide each screen its own TopAppBar and let it handle all the necessary logic. Aside from a lot of code duplication, each screen's TopAppBar will be recomposed on opening the screen, and, as described in this post, will flicker.
Solution 2 - not quite elegant
From now on I decided to have a single TopAppBar in my project's top level composable, that will depend on a state with current screen saved. Now we can easily implement logic for Tabs.
To solve the problem of screens opened from within a Tab, I extended Google's idea and implemented a general AppScreen class that represents every screen that can be opened:
// This class represents any screen - tabs and their subscreens.
// It is needed to appropriately change top app bar behavior
sealed class AppScreen(#StringRes val screenNameResource: Int) {
// Employee-related
object Employees: AppScreen(R.string.employees)
object EmployeeDetails: AppScreen(R.string.profile)
// Events-related
object Events: AppScreen(R.string.events)
object EventDetails: AppScreen(R.string.event)
object EventNew: AppScreen(R.string.event_new)
// Projects-related
object Projects: AppScreen(R.string.projects)
// Devices-related
object Devices: AppScreen(R.string.devices)
// Profile-related
object Profile: AppScreen(R.string.profile)
}
I then save it to a state in the top-level composable in the scope of TopAppBar and pass currentScreenHandler as an onNavigate argument to my Tab composables:
var currentScreen by remember { mutableStateOf(defaultTab.asScreen()) }
val currentScreenHandler: (AppScreen) -> Unit = {navigatedScreen -> currentScreen = navigatedScreen}
// Somewhere in the bodyContent of a Scaffold
when (currentTab) {
AppTab.Employees -> EmployeesTab(currentScreenHandler)
// And other tabs
// ...
}
And from inside the Tab composable:
val navController = rememberNavController()
NavHost(navController, startDestination = "employees") {
composable("employees") {
onNavigate(AppScreen.Employees)
Employees(it.hiltViewModel(), navController)
}
composable("employee/{userId}") {
onNavigate(AppScreen.EmployeeDetails)
Employee(it.hiltViewModel())
}
}
Now the TopAppBar in the root composable knows about higher-level screens and can implement necessary logic. But doing this for every subscreen of an app? A considerable amount of code duplication, and architecture of communication between this app bar and a composable it represents (how the composable reacts to actions performed on the app bar) is yet to be composed (pun intended).
Solution 3 - the best?
I implemented a viewModel for handling the needed logic, as it seemed like the most elegant solution:
#HiltViewModel
class AppBarViewModel #Inject constructor() : ViewModel() {
private val defaultTab = AppTab.Events
private val _currentScreen = MutableStateFlow(defaultTab.asScreen())
val currentScreen: StateFlow<AppScreen> = _currentScreen
fun onNavigate(screen: AppScreen) {
_currentScreen.value = screen
}
}
Root composable:
val currentScreen by appBarViewModel.currentScreen.collectAsState()
But it didn't solve the code duplication problem of the second solution. First of all, I had to pass this viewModel to the root composable from MainActivity, as there appears to be no other way of accessing it from inside a composable. So now, instead of passing a currentScreenHandler to Tab composables, I pass a viewModel to them, and instead of calling the handler on navigate event, I call viewModel.onNavigate(AppScreen), so there's even more code! At least, I maybe can implement a communication mechanism mentioned in the previous solution.
The question
For now the second solution seems to be the best in terms of code amount, but the third one allows for communication and more flexibility down the line for some yet to be requested features. I may be missing something obvious and elegant. Which of my implementations you consider the best, and if none, what would you do to solve this problem?
Thank you.
I use a single TopAppBar in the Scaffold and use a different title, drop-down menu, icons, etc by raising events from the Composables. That way, I can use just a single TopAppBar with different values. Here is an example:
val navController = rememberNavController()
var canPop by remember { mutableStateOf(false) }
var appTitle by remember { mutableStateOf("") }
var showFab by remember { mutableStateOf(false) }
var showDropdownMenu by remember { mutableStateOf(false) }
var dropdownMenuExpanded by remember { mutableStateOf(false) }
var dropdownMenuName by remember { mutableStateOf("") }
var topAppBarIconsName by remember { mutableStateOf("") }
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
val tourViewModel: TourViewModel = viewModel()
val clientViewModel: ClientViewModel = viewModel()
navController.addOnDestinationChangedListener { controller, _, _ ->
canPop = controller.previousBackStackEntry != null
}
val navigationIcon: (#Composable () -> Unit)? =
if (canPop) {
{
IconButton(onClick = { navController.popBackStack() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back Arrow"
)
}
}
} else {
{
IconButton(onClick = {
scope.launch {
scaffoldState.drawerState.apply {
if (isClosed) open() else close()
}
}
}) {
Icon(Icons.Filled.Menu, contentDescription = null)
}
}
}
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
DrawerContents(
navController,
onMenuItemClick = { scope.launch { scaffoldState.drawerState.close() } })
},
topBar = {
TopAppBar(
title = { Text(appTitle) },
navigationIcon = navigationIcon,
elevation = 8.dp,
actions = {
when (topAppBarIconsName) {
"ClientDirectoryScreenIcons" -> {
// search icon on client directory screen
IconButton(onClick = {
clientViewModel.toggleSearchBar()
}) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = "Search Contacts"
)
}
}
}
if (showDropdownMenu) {
IconButton(onClick = { dropdownMenuExpanded = true }) {
Icon(imageVector = Icons.Filled.MoreVert, contentDescription = null)
DropdownMenu(
expanded = dropdownMenuExpanded,
onDismissRequest = { dropdownMenuExpanded = false }
) {
// show different dropdowns based on different screens
when (dropdownMenuName) {
"ClientDirectoryScreenDropdown" -> ClientDirectoryScreenDropdown(
onDropdownMenuExpanded = { dropdownMenuExpanded = it })
}
}
}
}
}
)
},
...
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
NavHost(
navController = navController,
startDestination = Screen.Tours.route
) {
composable(Screen.Tours.route) {
TourScreen(
tourViewModel = tourViewModel,
onSetAppTitle = { appTitle = it },
onShowDropdownMenu = { showDropdownMenu = it },
onTopAppBarIconsName = { topAppBarIconsName = it }
)
}
Then set the TopAppBar values from different screens like this:
#Composable
fun TourScreen(
tourViewModel: TourViewModel,
onSetAppTitle: (String) -> Unit,
onShowDropdownMenu: (Boolean) -> Unit,
onTopAppBarIconsName: (String) -> Unit
) {
LaunchedEffect(Unit) {
onSetAppTitle("Tours")
onShowDropdownMenu(false)
onTopAppBarIconsName("")
}
...
Not probably the perfect way of doing it, but no duplicate code.
According to this example I implemented shared viewModels in a nested navigation graph.
Setup
Nested Graph:
private fun NavGraphBuilder.accountGraph(navController: NavHostController) {
navigation(
startDestination = "main",
route = "account") {
composable("main") {
val vm = hiltViewModel<AccountViewModel(navController.getBackStackEntry("account"))
//... ui ...
}
composable("login") {
val vm = hiltViewModel<AccountViewModel(navController.getBackStackEntry("account"))
//... ui ...
}
}
}
NavHost:
#Composable
private fun NavHost(navController: NavHostController, modifier: Modifier = Modifier){
NavHost(
navController = navController,
startDestination = MainScreen.Home.route,
modifier = modifier
) {
composable("home") { HomeScreen(hiltViewModel()) }
composable("otherRoute") { OtherScreen(hiltViewModel()) }
accountGraph(navController)
}
}
BottomNavBar:
#Composable
private fun ButtonNav(navController: NavHostController) {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
items.forEach { screen ->
BottomNavigationItem(
icon = { ... },
label = { ... },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
// 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
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route) { 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
}
}
)
}
}
}
Problem
With this setup if I naviagte to "account" (the nested graph) and back to any other route I get the error:
java.lang.IllegalArgumentException: No destination with route account is on the NavController's back stack. The current destination is Destination(0x78dd8526) route=otherRoute
Assumptions / Research Results
BottomNavItem
The exception did not occure when I remove the popUpTo(route) onClick. But then I ended up with a large stack.
lifecycle of backStackEntry
Have a look at the following:
//...
composable("main") { backStackEntry ->
val vm = hiltViewModel<AccountViewModel(navController.getBackStackEntry("account"))
//... ui ...
}
//...
I found out when navigating back the composable which will be left will be recomposed but in this case the backStackEntry seams to have another lifecycle.currentState because if I wrap the whole composable like this:
//...
composable("main") { backStackEntry ->
if(backStackEntry.lifecycle.currentState == Lifecycle.State.RESUMED){
val vm = hiltViewModel<AccountViewModel(navController.getBackStackEntry("account"))
//... ui ...
}
}
//...
... the exception did not occure.
The idea with the lifecycle issue came into my mind when I saw that the offical example has similar workarounds in place.
Summary
I actually do not know if I did something wrong or if I miss a conecept here. I can put the lifecycle-check-workaround into place but is this really as intended? Additional to that I did not find any hint in the doc regarding that.
Does anybody know how to fix that in a proper way?
Regards,
Chris
This is how you do it now but make sure you have the latest compose navigation artefacts:
private fun NavGraphBuilder.accountGraph(navController: NavHostController) {
navigation(
startDestination = "main",
route = "account") {
composable("main") {
val parentEntry = remember {
navController.getBackstackEntry("account")
}
val vm = hiltViewModel<AccountViewModel(parentEntry)
//... ui ...
}
composable("login") {
val parentEntry = remember {
navController.getBackstackEntry("account")
}
val vm = hiltViewModel<AccountViewModel(parentEntry)
//... ui ...
}
}
}
There was an issue with the navigation component. It has been fixed for me with v2.4.0-alpha08
I want to have a bottom navigation bar with two items/screens: Order and Account. Order is the start destination. Order has its own navigation and it has two screens: ItemList and ItemDetail. ItemDetail opens when an item is clicked in ItemList screen.
When I run the app, I can see the ItemList screen but Order item in the bottom navigation bar is not selected. If I click on Account item, I can see Account screen and Account item gets selected in the bottom navigation bar.
I think this is happening because of the recomposition: when Order is selected at the beginning since it is the start destination, its nested graph is called and a new destination (ItemList) is navigated, leading a recomposition, with currentRoute being "itemList" rather than "order".
How can I get Order icon selected in the bottom navigation bar? Is there a recommended what of handling nested graphs with bottom nav?
This is what I have at the moment:
object Destinations {
const val ORDER_ROUTE = "order"
const val ACCOUNT_ROUTE = "account"
const val ITEM_LIST_ROUTE = "itemList"
const val ITEM_DETAIL_ROUTE = "itemDetail"
const val ITEM_DETAIL_ID_KEY = "itemId"
}
class NavigationActions(navController: NavHostController) {
val selectItem: (Long) -> Unit = { itemId: Long ->
navController.navigate("${Destinations.ITEM_DETAIL_ROUTE}/$itemId")
}
val upPress: () -> Unit = {
navController.navigateUp()
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
#Compose
fun MyApp() {
MyAppTheme {
val navController = rememberNavController()
val tabs = listOf(Destinations.ORDER_ROUTE, Destinations.ACCOUNT_ROUTE)
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
Scaffold(
bottomBar = {
BottomNavigation {
tabs.forEach { tab ->
BottomNavigationItem(
icon = { Icons.Filled.Favorite },
label = { Text(tab) },
selected = currentRoute == tab,
onClick = {
navController.navigate(tab) {
popUpTo = navController.graph.startDestination
launchSingleTop = true
}
},
alwaysShowLabel = true,
selectedContentColor = MaterialTheme.colors.secondary,
unselectedContentColor = LocalContentColor.current
)
}
}
}
) {
NavGraph(navController)
}
}
}
#Composable
fun NavGraph(
navController: NavHostController,
startDestination: String = Destinations.ORDER_ROUTE
) {
val actions = remember(navController) { NavigationActions(navController) }
NavHost(navController = navController, startDestination = startDestination) {
navigation(startDestination = Destinations.ITEM_LIST_ROUTE, route = Destinations.ORDER_ROUTE) {
composable(Destinations.ITEM_LIST_ROUTE) {
ItemList(actions.selectItem)
}
composable(
"${Destinations.ITEM_DETAIL_ROUTE}/{$Destinations.ITEM_DETAIL_ID_KEY}",
arguments = listOf(navArgument(Destinations.ITEM_DETAIL_ID_KEY) {
type = NavType.LongType
})
) {
ItemDetail()
}
}
composable(Destinations.ACCOUNT_ROUTE) {
Account()
}
}
}
I wrote this article with a similar example. It's in Portuguese but if you translate the page to English you'll get the idea... Also, you can find the sources here.
I think the problem is happening because you're using just one NavHost for the entire app. In fact, I guess you need to use one NavHost for each tab, then when the user select a tab, you must change the current NavHost.
oh! my article is based on this post here, which can also help you.