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.
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 ofnavController
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:
- Pass only parsed arguments into your composable
- 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
).