Closed StephanSchuster closed 2 years ago
Hi again @StephanSchuster!
In these cases, all navigation APIs from the official compose navigation applies.
You can do:
navController.navigate(Screen1.route)
for example. If Screen1 does not have navigation arguments.
There is also a navigateTo
extension function of NavController
that accepts a Routed
instance. So that would be:
navController.navigateTo(Screen1)
. This is just a convenience method, it does the same as using the jetpack component API one.
To get the navController
you can use the rememberDestinationsNavController
function. You then need to pass that navController
to the DestinationsNavHost
function.
Thank you @raamcosta for your immediate response.
I guess it's too late. I was already watching the sources of the extension method you also mentioned and then missed the "...To()" and got confused. My mistake. All clear now. Maybe at some point a very simple but working example in the readme would help newbies like me. Your official sample has lots of non-navigation related code in it and the docs only mention code fragments.
More important: Now my app crashes.
java.lang.ClassCastException: java.util.LinkedHashSet cannot be cast to java.util.List
at com.google.accompanist.navigation.animation.AnimatedNavHostKt.AnimatedNavHost$lambda-3(AnimatedNavHost.kt:388)
at com.google.accompanist.navigation.animation.AnimatedNavHostKt.AnimatedNavHost(AnimatedNavHost.kt:165)
at com.google.accompanist.navigation.animation.AnimatedNavHostKt.AnimatedNavHost(AnimatedNavHost.kt:91)
at com.ramcosta.composedestinations.DestinationsNavHostKt.DestinationsNavHost(DestinationsNavHost.kt:49)
at com.example.nav.ScreensKt.Scaffold(Screens.kt:30)
My versions:
composeVersion = '1.0.4'
accompanistVersion = '0.20.0'
composeNavigationVersion = '2.4.0-beta01'
That brings me to my last questions (for today):
Okay, it seems to work after updating to the latest versions:
composeVersion = '1.0.5'
accompanistVersion = '0.20.2'
composeNavigationVersion = '2.4.0-beta02'
If possible, I would still appreciate a comment to my last question. Thanks.
Yeah, the "To()" suffix is just there so that imports on the IDE don't get all wonky. I will add a sub section on the navigation section explaining navigation above the NavHost level.
Yes, that crash was not directly an issue with this library. If you were using those same versions and doing the navigation code manually, that crash would still happen.
Regarding the future and the versions, I don't expect there to be many restrictions. For now, since the APIs we rely on are not final, and there are a lot of changes happening in their implementation, I include a "tested versions" block in each release. But since this library works as a wrapper to other APIs, once those get stable that won't be needed anymore.
Ah, alright. Understood. Thanks for clarification.
I now have the the following code with above versions:
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun Scaffold() {
Row(modifier = Modifier.fillMaxSize()) {
val navController = rememberDestinationsNavController()
DestinationsMenu(navController = navController)
DestinationsNavHost(
modifier = Modifier.fillMaxSize(),
navController = navController
)
}
}
@Composable
fun DestinationsMenu(navController: NavController) {
val backStackEntry by navController.currentBackStackEntryAsState()
val destination = backStackEntry?.navDestination ?: NavGraphs.root.startDestination
NavigationRail {
NavGraphs.root.destinations.forEach {
NavigationRailItem(
icon = { Icon(Icons.Filled.Star, contentDescription = it.key) },
label = { Text(it.key) },
selected = it.value == destination,
onClick = { navController.navigate(it.value.route) }
)
}
}
}
The app starts and then immediately crashes:
java.lang.NoSuchMethodError: No static method AnimatedContent(Landroidx/compose/animation/core/Transition;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Alignment;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V in class Landroidx/compose/animation/AnimatedContentKt; or its super classes (declaration of 'androidx.compose.animation.AnimatedContentKt' appears in /data/app/~~5AEd6a9JA-Yz6TYBzgaeXA==/com.elektrobit.mad.template-4U9S7JjqsHbSKJj0LUx2tA==/base.apk)
at com.google.accompanist.navigation.animation.AnimatedNavHostKt.AnimatedNavHost(AnimatedNavHost.kt:242)
at com.google.accompanist.navigation.animation.AnimatedNavHostKt$AnimatedNavHost$10.invoke(Unknown Source:23)
at com.google.accompanist.navigation.animation.AnimatedNavHostKt$AnimatedNavHost$10.invoke(Unknown Source:10)
at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:140)
at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2158)
at androidx.compose.runtime.ComposerImpl.skipToGroupEnd(Composer.kt:2427)
at androidx.compose.material.MaterialTheme_androidKt.PlatformMaterialTheme(MaterialTheme.android.kt:24)
at androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:82)
at androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:81)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:140)
at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2158)
at androidx.compose.runtime.ComposerImpl.skipToGroupEnd(Composer.kt:2427)
at androidx.compose.material.TextKt.ProvideTextStyle(Text.kt:266)
at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:81)
at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:80)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.material.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:72)
at com.example.navpoc.template.common.theme.ThemeKt$TemplateTheme$1.invoke(Theme.kt:32)
at com.example.navpoc.template.common.theme.ThemeKt$TemplateTheme$1.invoke(Theme.kt:31)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at com.example.navpoc.template.common.theme.ThemeKt.TemplateTheme(Theme.kt:29)
at com.example.navpoc.template.common.theme.ThemeKt$TemplateTheme$2.invoke(Unknown Source:10)
at com.example.navpoc.template.common.theme.ThemeKt$TemplateTheme$2.invoke(Unknown Source:10)
at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:140)
at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2158)
at androidx.compose.runtime.ComposerImpl.skipToGroupEnd(Composer.kt:2427)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:252)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:251)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
This most probably has nothing to do with this library but with Accompanist navigation. Still, I wanted to ask if you have any idea.
I then removed the accompanist-navigation-animation
dependency from my gradle file. To my understanding the code you generate is then different (right?) and I was hoping to get rid of above issue. But on build I then get the following error:
e: ...\app\build\generated\ksp\debug\kotlin\com\ramcosta\composedestinations\DestinationsNavHost.kt: (38, 77): Unresolved reference: NavBackStackEntry
e: ...\app\build\generated\ksp\debug\kotlin\com\ramcosta\composedestinations\DestinationsNavHost.kt: (69, 77): Unresolved reference: NavBackStackEntry
e: ...\app\build\generated\ksp\debug\kotlin\com\ramcosta\composedestinations\DestinationsNavHost.kt: (99, 77): Unresolved reference: NavBackStackEntry
e: ...\app\build\generated\ksp\debug\kotlin\com\ramcosta\composedestinations\DestinationsNavHost.kt: (119, 77): Unresolved reference: NavBackStackEntry
Looking at the generated code, I don't see an error:
package com.ramcosta.composedestinations
import androidx.compose.animation.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.*
import androidx.navigation.Navigator
import com.ramcosta.composedestinations.spec.DestinationSpec
import com.ramcosta.composedestinations.spec.DestinationStyle
import com.ramcosta.composedestinations.spec.NavGraphSpec
import com.ramcosta.composedestinations.navigation.DependenciesContainerBuilder
import com.ramcosta.composedestinations.navigation.dependency
//region NavHost
/**
* Like [androidx.navigation.compose.NavHost] but includes the destinations of [navGraph].
* Composables annotated with `@Destination` will belong to a [NavGraph] inside [NavGraphs].
*
* @see [androidx.navigation.compose.NavHost]
*
* @param modifier [Modifier]
* @param startDestination the start destination to use
* @param navController [NavHostController]
* @param dependenciesContainerBuilder lambda invoked when a destination gets navigated to. It allows
* the caller to contribute certain dependencies that the destination can use.
*/
@Composable
fun DestinationsNavHost(
modifier: Modifier = Modifier,
startDestination: Destination = NavGraphs.root.startDestination,
navController: NavHostController = rememberDestinationsNavController(),
dependenciesContainerBuilder: @Composable DependenciesContainerBuilder.(NavBackStackEntry) -> Unit = {}
) {
NavHost(
navController = navController,
startDestination = startDestination.route,
modifier = modifier,
route = NavGraphs.root.route,
) {
addNavGraphDestinations(
navGraphSpec = NavGraphs.root,
addNavigation = addNavigation(),
addComposable = addComposable(navController, dependenciesContainerBuilder)
)
}
}
//endregion NavHost
//region NavController
/**
* Wraps the correct `remember*NavController` method depending on
* whether animations are available or not.
*/
@Composable
fun rememberDestinationsNavController(
vararg navigators: Navigator<out NavDestination>
) = rememberNavController(*navigators)
//endregion
//region internals
private fun addComposable(
navController: NavHostController,
dependenciesContainerBuilder: @Composable DependenciesContainerBuilder.(NavBackStackEntry) -> Unit
): NavGraphBuilder.(DestinationSpec) -> Unit {
return { destination ->
destination as Destination
when (val destinationStyle = destination.style) {
is DestinationStyle.Default -> {
addComposable(
destination,
navController,
dependenciesContainerBuilder
)
}
is DestinationStyle.Dialog -> {
addDialogComposable(
destinationStyle,
destination,
navController,
dependenciesContainerBuilder
)
}
else -> throw RuntimeException("Should be impossible! Code gen should have failed if using a style for which you don't have the dependency")
}
}
}
private fun NavGraphBuilder.addComposable(
destination: Destination,
navController: NavHostController,
dependenciesContainerBuilder: @Composable DependenciesContainerBuilder.(NavBackStackEntry) -> Unit
) {
composable(
route = destination.route,
arguments = destination.arguments,
deepLinks = destination.deepLinks
) { navBackStackEntry ->
destination.Content(
navController,
navBackStackEntry
) {
dependenciesContainerBuilder(navBackStackEntry)
}
}
}
private fun NavGraphBuilder.addDialogComposable(
dialogStyle: DestinationStyle.Dialog,
destination: Destination,
navController: NavHostController,
dependenciesContainerBuilder: @Composable DependenciesContainerBuilder.(NavBackStackEntry) -> Unit
) {
dialog(
destination.route,
destination.arguments,
destination.deepLinks,
dialogStyle.properties
) { navBackStackEntry ->
destination.Content(
navController,
navBackStackEntry
) { dependenciesContainerBuilder(navBackStackEntry) }
}
}
private fun addNavigation(): NavGraphBuilder.(NavGraphSpec, NavGraphBuilder.() -> Unit) -> Unit {
return { navGraph, builder ->
navigation(
navGraph.startDestination.route,
navGraph.route
) {
this.builder()
}
}
}
//endregion
Any idea?
Daaamn, yes I know what was the problem π
My bad with the latest version, I introduced the import for that in the accompanist specific imports π€¦ I definitely need to streamline my testing with and without accompanist so that this doesn't happen again. I will release a hotfix for this right now, it should be up in maybe 30min or so.
Thanks again for everything. And I'm sorry about this. I will definitely take measures so that it doesn't happen again.
I will comment here once the new version is live on maven central.
Oh wow, that sounds great.
The latest possible versions currently seem to be:
composeVersion = '1.1.0-beta02' <-- latest
composeNavigationVersion = '2.4.0-beta02' <-- latest
accompanistVersion = '0.21.0-beta' <-- not latest
If you release another version anyways, would it be possible to make the latest accompanist 0.21.2-beta
work? Currently your generated code does not compile with this.
I was working on a new release to support all the new versions but I really want to release this hotfix first.
Later today (or maybe tomorrow) I will make another release to support the new accompanist version.
Done. Artifact with version 0.9.2-beta is available on maven central π
Once again, thank you so much for finding this :) Please check if that is solved for you when you remove accompanist.
As for the other crash you were having, it indeed seems like an issue with that library. Try the versions I'm using in the sample app:
const val compose = "1.1.0-beta01"
const val composeNavigation = "2.4.0-beta01"
const val accompanist = "0.21.0-beta"
And let me know too if that works.
Thanks for your support.
With accompanist 0.21.0-beta
the beta-01
and also the beta-02
of both compose libraries work. I tested them all. As said before, it did work even without your hotfix.
With accompanist 0.21.2-beta
(latest version and said to be compatible with compose 1.1.0-beta02
) I get compile issues with your library. Would be nice to get them fixed soon.
Regarding the Unresolved reference: NavBackStackEntry
:
It occurs with:
composeVersion = '1.1.0-beta02'
composeNavigationVersion = '2.4.0-beta02'
accompanistVersion = '0.20.2'
composeDestinationsVersion = '0.9.1-beta'
It does not occur anymore with your HOTFIX:
composeVersion = '1.1.0-beta02'
composeNavigationVersion = '2.4.0-beta02'
accompanistVersion = '0.20.2'
composeDestinationsVersion = '0.9.2-beta'
Nice π
Later today or tomorrow morning I'll be releasing the new version to work with latest accompanist.
It envolves some API changes (forced by changes on accompanist) and that's why I want some more time to test and document them.
An thank you so much again for this π
0.9.4 is now up with support for the new accompanist version π
Let me know how it goes!
Closing this as it should be solved. Thank you so much π
What is the minimal and cleanest way to make the following navigation between three screens working:
From what I understand from your Wiki and by looking at your sample, I can only get a
DestinationsNavigator
impl instance as a param to my screens (via generated code). But in this simple case I need it outside.rememberDestinationsNavController
seems to not help me either, since itsnavigate(...)
does not accept myScreenXDestination
s.Is this not a valid use case? What did I misunderstand?