Hey folks, first of all kudos on this kickass comm...
# compose
f
Hey folks, first of all kudos on this kickass community, thanks to all the mods and organisers. I was actually wondering if there’s a way to use the Compose Navigation Library to create the following graph with a slight intricasy.
Copy code
1. Home
2. Onboarding -
       * Login
       * Username
       * Name
       * Finish Screen
The nav needs to go back to Home, from any of the nested screens in the onboarding flow. But also, these two graphs do not share the same navController, since I have an overarching UI component, on the top of all the screens in the onboarding. How can I achieve a clean and properly functioning Nav Graph.
s
But also, these two graphs do not share the same navController
Could you explain a bit more about why that is the case? You should be able to make it all work with one NavHost as I understand your use case.
f
The problem is that the Onboarding flow is encapsulated in a screen composable, where there is a linear progress component on the top of the screen, and the nested screens just navigate below the component. For me to have this layout, it was impossible to use the same navController. but let me know If I am wrong here.
@Stylianos Gakis
s
You don't need to tag me in a thread in which I am already a part of. I get the notification anyway. You could consider using a shared element for the progress bar. But that may be a bit too experiemental for you, so let's just go forward with the multi NavHost solution. So if your Onboarding is one composable in your original NavHost which contains Home and Onboarding, can't you just do
navController.popUpTo<Onboarding> { inclusive = true }
on that outer NavHost? That would get rid of your entire onboarding in one go.
f
Sorry about the tag, didnt know it. Also, yep, I feel like I can use
popUpto
to pop the Onboarding navHost, but how do I navigate the Home NavHost to the HomeScreen then? Will it not land on an empty screen if I just pop the nested navgraph?
s
No worries 😊 That's why I said it, so you know for future conversations as well. Oh, so when you are in
Onboarding
then your
Home
is not on the stack? If that is the case you can instead do this
Copy code
navController.navigate(Home) {
  popUpTo<Onboarding> {
    inclusive = true
  }
}
And it should both pop onboarding and go to Home
1
f
Nope the
Home
is not in the stack. And yeah I was doing this, but since this
navController
is a different object, than the parent Nav Graph’s, it crashes saying it doesnt recognise the destination
home
.
s
Why is
Onboarding
and
Home
in different NavHosts? You should have
Home
and
Onboarding
in the same one big NavHost. Then inside the
Onboarding
destination, in there you should have another NavHost, with a completely separate nav graph like this
Copy code
* Login // Also make this the start destination
* Username
* Name
* Finish Screen
How did you navigate to Onboarding in the first place if they are not on the same NavController?
You know what, if you paste more of your code here it would make it much easier to reason about. I feel like you're calling the navigation event on the wrong NavController
f
No No, I think I miscommunicated. The
Onboarding
and
Home
screens are indeed in the same NavHost. The big one. I’ll post some code for your ref.
The Main Nav graph
Copy code
NavHost(
        navController = navController,
        startDestination = startDestination
    ) {

        composable<SpringchatScreen.OnboardingScreen> {
            OnboardingRoot(
                startingOnboardingLevel = onboardingLevel ?: OnboardingLevel.AUTHENTICATION
            )
        }

        composable<SpringchatScreen.Home> {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {

                Text("Home")
            }
        }
    }
This is the Onboarding nav graph inside the Onboarding Screen composable
Copy code
val navController = rememberNavController()

NavHost(
    navController = navController,
    startDestination = startingDestination,
    enterTransition = {
        slideInHorizontally(
            initialOffsetX = { 1000 }
        )
    },
    exitTransition = {
        slideOutHorizontally(
            targetOffsetX = { -1000 }
        )
    },
) {

    composable<OnboardingScreen.PhoneNumberAuthScreen> {
        LaunchedEffect(Unit) {
            currentOnboardingLevel = OnboardingLevel.AUTHENTICATION
        }
        PhoneNumberAuthScreen(
            onNext = {

                navController.navigate(SpringchatScreen.Home) {
                    popUpTo<SpringchatScreen.OnboardingScreen> {
                        inclusive = true
                    }
                }
            }
    }
    )
}
}
So the code
navController.navigate(SpringchatScreen.Home)
crashes , with the exception I mentioned above. Which is logical since this navController doesnt know about the parent /bigger/home nav graph and its destinations like
SpringchatScreen.Home
s
Sure, change your OnboardingRoot composable to take a lambda which pops it up completely
Copy code
composable<SpringchatScreen.OnboardingScreen> {
    OnboardingRoot(
        startingOnboardingLevel = onboardingLevel ?: OnboardingLevel.AUTHENTICATION,
        closeOnboarding = {
            navController.navigate(SpringchatScreen.Home) {
                popUpTo<SpringchatScreen.OnboardingScreen> {
                    inclusive = true
                }
            }
        }
    )
}
And then hook it up to your step which wanted to do the pop. So if you did it from inside
PhoneNumberAuthScreen
, now do:
Copy code
PhoneNumberAuthScreen(
    onNext = { closeOnboarding() }
)
f
Ah yeah makes sense. 🤦 Thanks man.
s
Yup, you just need to call the function on the right NavController, since you had two. It's a good reason why in general you don't want to have more than one NavControllers.
f
yeah i didnt want to as well. because scenarios like these might make the code more cluttered in the future. Is there any way I could reuse the same navController for both the graphs ( essentially making a nested nav graph), but still have an overarching encapsulating composable wrapping the inner nav graph?
s
Yeah that would in fact be tricky. You'd either have to have that wrapper around the entire NavHost, which you don't want to do of course, it would wrap all other destinations as well. You could also consider using a progress bar per-screen and try to make it "feel" like it's outside of the screen by making use of the shared element transition APIs. You could mark the top app bar as a shared element, so that it would stay where it is as the rest of the screen animates. Now that could perhaps be tricky with if the progress bar also needs to animate while the transition is happening, I am not sure how that would play out. I'd have to try it myself
f
Yeah , the last part is essential. But I’ll still look into it. May be as a shared element, it is able to have its own changes rendered while being transported across different composables. Anyway thanks a lot.
s
No problem, glad I could help! Yeah I haven't tried shared element + animation inside the content itself to be honest. If you do try it please let me know here, I'd like to know myself 😊 I've only tried it with static contents like this
In this one you can even see an example of the TopAppBar being a shared element, and it looks like it stays in-place, even though it's just part of each screen individually
f
Woah, this looks exactly like what I want. I’ll check it out.
s
99% of the diff is noise. The important part is this https://github.com/HedvigInsurance/android/pull/2071/files?diff=unified&amp;w=1#diff-87d59c43de8dc165ffe66f1cf498f1825247c25caa996e15d4cc6a972e8c6a57 where you add the
.sharedElement(
on the TopAppBar, and make sure that both two screens pass the same key in the
sharedElement
modifier.
This PR https://github.com/android/compose-samples/pull/1314/files#diff-08ecbbcf059d957f1cd966e835f59b9fd85558692632dd369652cc9a17888260 is probably a bit easier to go through, where they add shared elements in jetsnack sample app
f
Cool, I’ll check it out. 🙂