The app I'm building uses compose navigation with routes. The challenge is that the start destination is dynamic.
Here is a minimal example:
class MainActivity : ComponentActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "dynamic/1", // doesn't work
// startDestination = "static", // workaround
) {
composable(
route = "dynamic/{$ARG_ID}",
arguments = listOf(navArgument(ARG_ID) { type = NavType.StringType }),
) {
val id = it.arguments?.getString(ARG_ID)
Text("dynamic route, received argument: $id!")
}
// part of the workaround
// composable(
// route = "static",
// ) {
// LaunchedEffect(this) {
// navController.navigate("dynamic/1")
// }
// }
}
}
}
companion object
{
const val ARG_ID = "id"
}
}
The app crashes with
java.lang.IllegalArgumentException: navigation destination route/1 is not a direct child of this NavGraph
The problem only exists if the "dynamic" route is used as start destination. This can be verified by using startDestination = "static"
.
Although, the "static" route workaround works I'm looking for a solution without it because it kind of obfuscates the code and also creates an additional entry in the back stack.
-> Full code sample to reproduce the issue
Related SO questions
Edit:
I want to stress that the original sample used to not contain the "static" composable. I only added the "static" composable to have a working startDestination
and to prove that the "dynamic" composable can be navigated to.
Update:
Even switching to the query parameter syntax for optional arguments , providing a default value, and setting the start destination without any argument does not work.
The following variation
NavHost(
navController = navController,
startDestination = "dynamic",
) {
composable(
route = "dynamic?$ARG_ID={$ARG_ID}",
arguments = listOf(navArgument(ARG_ID) { type = NavType.StringType; defaultValue = "1" }),
) {
val id = it.arguments?.getString(ARG_ID)
Text("dynamic route, received argument: $id!")
}
}
Leads to the exception
java.lang.IllegalArgumentException: navigation destination dynamic is not a direct child of this NavGraph
Full credit goes to ianhanniballake , who explained the solution to me in a comment. I'm going to show the working version of my code sample here.
The big insight to me was:
startDestination
must not match a composable route
in the sense of pattern matching but it must be exactly the same string .
That means an argument can't be set via startDestination
directly but has to be set via the argument's defaultValue
.
Here is the working sample:
class MainActivity : ComponentActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
NavHost(
navController = navController,
// 1st change: Set startDestination to the exact string of route
startDestination = "dynamic/{$ARG_ID}", // NOT "dynamic/1", provide arguments via defaultValue
) {
composable(
route = "dynamic/{$ARG_ID}",
// 2nd change: Set startDestination argument via defaultValue
arguments = listOf(navArgument(ARG_ID) { type = NavType.StringType; defaultValue = "1" }),
) {
val id = it.arguments?.getString(ARG_ID)
Text("dynamic route, received argument: $id!")
}
}
}
}
companion object
{
const val ARG_ID = "id"
}
}
The approach equally works with the argument provided in the form of a query parameter.
To be honest, I see this as a small limitation because the start route now dictates what has to be the defaultValue
. I might want to set a different defaultValue
or none at all. Yet, in most cases this should be the most elegant solution.
should not be using dynamic route value in "startDestination" NavHost --> navController.navigate(<dynamic route >)
All credit goes to ianhanniballake and Peter . In my case I didn't add any additional (mandatory key/optional key) in route for the argument data. I kept the route clean like below:
Nav graph:
navigation(route = Route.Root.route, startDestination = Route.SubmitForm.route) {
composable(
route = Route.SubmitForm.route,
arguments = listOf(
navArgument(ARG_KEY) {
type = NavType.StringType
defaultValue = JsonConverter.toJson(user, User::class.java)
},
)
)
}
Route sealed class:
sealed class Route(val route: String) {
object MyRoute : Route("$ROOT/submit-form")
}
And in view model just get the data like this:
@HiltViewModel
class MyViewModel @Inject constructor(
stateHandle: SavedStateHandle,
) : ViewModel {
lateinit var user
init {
user = stateHandle.get<String>(ARG_NAME) // Supported data types
}
}
It worked for me.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.