androidkotlindebuggingandroid-jetpack-composejetpack-compose-navigation

Navigation graph has not been set for NavController compose


This is the code. Seemingly everything is set up correctly but I cant seem to figure out why this does not work. I am navigating from 1 main screen to a detail screen after clicking a list item. I can provide more detail if needed. The navGraph is null.


@Composable
fun Navigation(navController: NavHostController, viewModel: GamesViewModel){
    NavHost(navController = navController, startDestination = Screen.MainScreen.route){
        composable(route = Screen.MainScreen.route) {
            OverallStanding(viewModel, navController)
        }

        navigation(startDestination = Screen.MainScreen.route, route = Screen.DetailsScreen.route){
            composable(route = Screen.DetailsScreen.route + "/{teamName}",
                arguments = listOf(navArgument("teamName"){
                    NavType.StringType
                    nullable = false
                })
            ) { entry ->
                GamesDetailScreen(viewModel, navController, teamName = entry.arguments?.getString("teamName"))
            }
        }
    }
}

This is the composable where I call the function

@Composable
fun TeamStatsItem(teamStatsItem: TeamStats, isMainScreen: Boolean) {
    val navController = rememberNavController()
    val lastColumnValue =
        if (isMainScreen) teamStatsItem.winPercentage.toString() + "%" else teamStatsItem.totalGamesPlayed.toString()
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clip(RoundedCornerShape(16.dp))
            .padding(12.dp)
            .height(55.dp)
            .background(Color.White)
            .clickable(
                true,
                onClick = { navController.navigate(Screen.DetailsScreen.withArguments(teamStatsItem.teamName)) }),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(text = teamStatsItem.teamName, modifier = Modifier.width(175.dp))
        Text(text = teamStatsItem.wins.toString(), modifier = Modifier.padding(end = 42.dp))
        Text(text = teamStatsItem.losses.toString(), modifier = Modifier.padding(end = 42.dp))
        Text(text = teamStatsItem.draws.toString(), modifier = Modifier.padding(end = 42.dp))
        Text(text = lastColumnValue)
    }
}

This is the error I recieved in full. It is from this function call to navigate in the composable above.

FATAL EXCEPTION: main
                                                                                                    Process: com.dushanesmith.yahoocodingexercise, PID: 29195
                                                                                                    java.lang.IllegalArgumentException: Cannot navigate to details_screen/Olympiacos. Navigation graph has not been set for NavController androidx.navigation.NavHostController@79d6b61.
                                                                                                        at androidx.navigation.NavController.navigate(NavController.kt:2375)
                                                                                                        at androidx.navigation.NavController.navigate$default(NavController.kt:2370)
                                                                                                        at com.dushanesmith.yahoocodingexercise.TeamStatsItemKt.TeamStatsItem$lambda$0(TeamStatsItem.kt:35)
                                                                                                        at com.dushanesmith.yahoocodingexercise.TeamStatsItemKt.$r8$lambda$TBskDp7ealVso3TrsxH8j1ss2Nw(Unknown Source:0)
                                                                                                        at com.dushanesmith.yahoocodingexercise.TeamStatsItemKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)
                                                                                                        at androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)
                                                                                                        at androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)
                                                                                                        at androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)
                                                                                                        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
                                                                                                        at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:179)
                                                                                                        at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:168)
                                                                                                        at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:474)
                                                                                                        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:508)
                                                                                                        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:497)
                                                                                                        at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:368)
                                                                                                        at androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNodeImpl$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:719)
                                                                                                        at androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNodeImpl.dispatchPointerEvent(SuspendingPointerInputFilter.kt:598)
                                                                                                        at androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNodeImpl.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:620)
                                                                                                        at androidx.compose.foundation.AbstractClickableNode.onPointerEvent-H0pRuoY(Clickable.kt:1044)
                                                                                                        at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:387)
                                                                                                        at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:373)
                                                                                                        at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:373)
                                                                                                        at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:229)
                                                                                                        at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:144)
                                                                                                        at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:120)
                                                                                                        at androidx.compose.ui.platform.AndroidComposeView.sendMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1999)
                                                                                                        at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1950)
                                                                                                        at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:1834)
                                                                                                        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
                                                                                                        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
                                                                                                        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
                                                                                                        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
                                                                                                        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
                                                                                                        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
                                                                                                        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
                                                                                                        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
                                                                                                        at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:490)
                                                                                                        at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1904)
                                                                                                        at android.app.Activity.dispatchTouchEvent(Activity.java:4377)
                                                                                                        at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:448)
2024-10-17 23:53:04.058 29195-29195 AndroidRuntime          com...hanesmith.yahoocodingexercise  E      at android.view.View.dispatchPointerEvent(View.java:15919)
                                                                                                        at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:7021)
                                                                                                        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6815)
                                                                                                        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6229)
                                                                                                        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6286)
                                                                                                        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6252)
                                                                                                        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6417)
                                                                                                        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6260)
                                                                                                        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6474)
                                                                                                        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6233)
                                                                                                        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6286)
                                                                                                        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6252)
                                                                                                        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6260)
                                                                                                        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6233)
                                                                                                        at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:9211)
                                                                                                        at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:9162)
                                                                                                        at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:9131)
                                                                                                        at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:9337)
                                                                                                        at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:267)
                                                                                                        at android.os.MessageQueue.nativePollOnce(Native Method)
                                                                                                        at android.os.MessageQueue.next(MessageQueue.java:335)
                                                                                                        at android.os.Looper.loopOnce(Looper.java:162)
                                                                                                        at android.os.Looper.loop(Looper.java:294)
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:8177)
                                                                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                                                                        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
                                                                                                        Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@41684e3, androidx.compose.runtime.BroadcastFrameClock@4a5eee0, StandaloneCoroutine{Cancelling}@aaf2599, AndroidUiDispatcher@75dd05e]

I tried changing the methods of defining the nav graph.


Solution

  • Your TeamStatsItem is using:

    val navController = rememberNavController()
    

    That's creating a brand new NavController that you've never associated with a NavHost, so that NavController does not have any navigation graph set on it, just like the error message says.

    You need to use the exact NavController you've passed to your NavHost.

    While you could pass it down through your composables like you did for your OverallStanding and GamesDetailScreen composables, that is not the best practice. As per the Navigation Compose Testing guide:

    Decouple the navigation code from your composable destinations to enable testing each composable in isolation, separate from the NavHost composable.

    This means that you shouldn't pass the navController directly into any composable and instead pass navigation callbacks as parameters. This allows all your composables to be individually testable, as they don't require an instance of navController in tests.

    The level of indirection provided by the composable lambda is what lets you separate your Navigation code from the composable itself. This works in two directions:

    1. Pass only parsed arguments into your composable
    2. Pass lambdas that should be triggered by the composable to navigate, rather than the NavController itself.

    So you'd instead write your composable as:

    @Composable
    fun TeamStatsItem(
        teamStatsItem: TeamStats,
        isMainScreen: Boolean,
        onTeamSelected: (teamName: String) -> Unit
    ) {
        val lastColumnValue =
            if (isMainScreen) teamStatsItem.winPercentage.toString() + "%" else teamStatsItem.totalGamesPlayed.toString()
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .clip(RoundedCornerShape(16.dp))
                .padding(12.dp)
                .height(55.dp)
                .background(Color.White)
                .clickable(
                    true,
                    onClick = { onTeamSelected(teamStatsItem.teamName) }),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Text(text = teamStatsItem.teamName, modifier = Modifier.width(175.dp))
            Text(text = teamStatsItem.wins.toString(), modifier = Modifier.padding(end = 42.dp))
            Text(text = teamStatsItem.losses.toString(), modifier = Modifier.padding(end = 42.dp))
            Text(text = teamStatsItem.draws.toString(), modifier = Modifier.padding(end = 42.dp))
            Text(text = lastColumnValue)
        }
    }
    

    This then lets you easily write a @Preview for this composable as well as write unit tests for it in isolation.

    The composable calling it would then call it like:

    TeamStatsItem(
        teamStatsItem,
        isMainScreen
    ) { teamName ->
        navController.navigate(Screen.DetailsScreen.withArguments(teamName)
    }
    

    Or pass the lambda up another layer, ideally all the way up to your NavHost level (which should be the only layer that actually has access to your NavController).