Search code examples
androidandroid-jetpack-composeandroid-architecture-navigationjetpack-compose-navigation

Compose Navigation - navigation destination ... is not a direct child of this NavGraph


I am trying to build below navigation for my order management app:

manage_orders/manage_orders/{locationId}
manage_orders/manage_order_details/{orderId}

And here is my navigation code for that:

internal sealed class Screen(val route: String) {
    object ManageOrders : Screen(manage_orders)
}

private sealed class LeafScreen(val route: String) {

    fun createRoute(root: Screen): String {
        return "${root.route}/$route"
    }

    object ManageOrders : LeafScreen("manage_orders/{locationId}") {

        fun createRoute(root: Screen, locationId: String): String {
            return "${root.route}/manage_orders/$locationId"
        }
    }

    object ManageOrderDetails : LeafScreen("manage_order_details/{orderId}") {

        fun createRoute(root: Screen, orderId: String): String {
            return "${root.route}/manage_order_details/$orderId"
        }
    }
}

@ExperimentalCoroutinesApi
@Composable
internal fun AppNavigation(
    navController: NavHostController,
    locationId: String,
    modifier: Modifier = Modifier
) {
    NavHost(
        navController = navController,
        startDestination = Screen.ManageOrders.route,
        modifier = modifier,
    ) {
        addManageOrdersTopLevel(navController, locationId)
    }
}

@ExperimentalCoroutinesApi
private fun NavGraphBuilder.addManageOrdersTopLevel(
    navController: NavHostController,
    locationId: String
) {
    navigation(
        route = Screen.ManageOrders.route,
        startDestination = LeafScreen.ManageOrders.createRoute(Screen.ManageOrders, locationId)
    ) {
        addManageOrders(navController = navController, root = Screen.ManageOrders)
        addManageOrderDetails(navController = navController, root = Screen.ManageOrders)
    }
}

@ExperimentalCoroutinesApi
private fun NavGraphBuilder.addManageOrders(
    navController: NavHostController,
    root: Screen
) {
    composable(
        route = LeafScreen.ManageOrders.createRoute(root),
        arguments = listOf(
            navArgument(LOCATION_ID) { type = NavType.StringType }
        )
    ) { backStackEntry ->
        backStackEntry.arguments?.let {
            ManageOrders(locationId = it.getString(LOCATION_ID)!!) { orderId ->
                navController.navigate(LeafScreen.ManageOrderDetails.createRoute(root, orderId))
            }
        }
    }
}

@ExperimentalCoroutinesApi
private fun NavGraphBuilder.addManageOrderDetails(
    navController: NavHostController,
    root: Screen
) {
    composable(
        route = LeafScreen.ManageOrderDetails.createRoute(root),
        arguments = listOf(
            navArgument(ORDER_ID) { type = NavType.StringType }
        )
    ) { backStackEntry ->
        backStackEntry.arguments?.let {
            ManageOrderDetails(
                navController = navController,
                orderId = it.getString(ORDER_ID)
            )
        }
    }
}

And here is the code to start the navigation:

class ManageOrderActivity : AppCompatActivity() {

    @ExperimentalCoroutinesApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            AppNavigation(
                navController = navController,
                locationId = intent.extras?.getString(KEY_LOCATION_ID) ?: ""
            )
        }
    }
}

However, I am getting below error:

FATAL EXCEPTION: main
Process: io.chanse.locals.cerve.qa, PID: 18285
java.lang.IllegalArgumentException: navigation destination -1881727488 is not a direct child of this NavGraph
    at androidx.navigation.NavGraphNavigator.navigate(NavGraphNavigator.kt:72)
    at androidx.navigation.NavGraphNavigator.navigate(NavGraphNavigator.kt:49)
    at androidx.navigation.NavController.navigateInternal(NavController.kt:189)
    at androidx.navigation.NavController.navigate(NavController.kt:1491)
    at androidx.navigation.NavController.onGraphCreated(NavController.kt:913)
    at androidx.navigation.NavController.setGraph(NavController.kt:852)
    at androidx.navigation.NavController.setGraph(NavController.kt:90)
    at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:113)
    at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:112)
    at androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:81)
    at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:781)
    at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:639)
    at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:733)
    at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:432)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:144)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:135)
    at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:727)
    at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:135)
    at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:187)
    at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
    at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:196)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:142)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:135)
    at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:814)
    at android.view.View.dispatchAttachedToWindow(View.java:22010)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:4291)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:4298)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:4298)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:4298)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:4298)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:4298)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:4298)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3135)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2618)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9971)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1010)
    at android.view.Choreographer.doCallbacks(Choreographer.java:809)
    at android.view.Choreographer.doFrame(Choreographer.java:744)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:995)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:246)
    at android.app.ActivityThread.main(ActivityThread.java:8512)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1139)

What's the problem here?

Update 1 (as per Ian's solution)

@Composable
internal fun AppNavigation(
    navController: NavHostController,
    locationId: String,
    modifier: Modifier = Modifier
) {
    NavHost(
        navController = navController,
        startDestination = LeafScreen.ManageOrders.route,
        modifier = modifier,
    ) {
        addManageOrdersTopLevel(navController, locationId)
    }
}

@ExperimentalCoroutinesApi
private fun NavGraphBuilder.addManageOrdersTopLevel(
    navController: NavHostController,
    locationId: String
) {
    navigation(
        route = Screen.ManageOrders.route,
        startDestination = LeafScreen.ManageOrders.createRoute(Screen.ManageOrders)
    ) {
        addManageOrders(
            navController = navController,
            root = Screen.ManageOrders,
            locationId = locationId
        )
    }
}

private fun NavGraphBuilder.addManageOrders(
    navController: NavHostController,
    root: Screen,
    locationId: String
) {
    composable(
        route = LeafScreen.ManageOrders.createRoute(root),
        arguments = listOf(
            navArgument(LOCATION_ID) {
                type = NavType.StringType
                defaultValue = locationId
            }
        )
    ) { backStackEntry ->
        backStackEntry.arguments?.let {
            ManageOrders(locationId = it.getString(LOCATION_ID)!!) { orderId ->
                navController.navigate(LeafScreen.ManageOrderDetails.createRoute(root, orderId))
            }
        }
    }
}

But still facing the same issue. Look's like I failed to understand what Ian sggested. What have I missed?


Solution

  • This line:

    startDestination = LeafScreen.ManageOrders.createRoute(Screen.ManageOrders, locationId)
    

    Does not match any of the route parameters on your destination. For example, your Screen.ManageOrders's route is:

    route = LeafScreen.ManageOrders.createRoute(root)
    

    The startDestination needs to match a route exactly. That means you need to be using

    startDestination = LeafScreen.ManageOrders.createRoute(root)
    

    If you want to set a locationId to be used for your start destination, you should set a defaultValue on your argument:

    @ExperimentalCoroutinesApi
    private fun NavGraphBuilder.addManageOrdersTopLevel(
        navController: NavHostController,
        locationId: String
    ) {
        navigation(
            route = Screen.ManageOrders.route,
            startDestination = LeafScreen.ManageOrders.createRoute(root)
        ) {
            addManageOrders(
                navController = navController,
                root = Screen.ManageOrders,
                locationId = locationId
            )
            addManageOrderDetails(navController = navController, root = Screen.ManageOrders)
        }
    }
    
    @ExperimentalCoroutinesApi
    private fun NavGraphBuilder.addManageOrders(
        navController: NavHostController,
        root: Screen,
        locationId: String
    ) {
        composable(
            route = LeafScreen.ManageOrders.createRoute(root),
            arguments = listOf(
                navArgument(LOCATION_ID) {
                    type = NavType.StringType
                    defaultValue = locationId
                }
            )
        ) { backStackEntry ->
            backStackEntry.arguments?.let {
                ManageOrders(locationId = it.getString(LOCATION_ID)!!) { orderId ->
                    navController.navigate(LeafScreen.ManageOrderDetails.createRoute(root, orderId))
                }
            }
        }
    }