Is it an anti-pattern to trigger a navigation chan...
# compose
j
Is it an anti-pattern to trigger a navigation change based on some state? For example, if I had a
hasCompletedOnboarding
state would it be improper to trigger a navigation change to the
onboarding
navigation graph from the
home
graph based on the given state? The thought would be to use
LaunchedEffect
so that the navigation would only happen when the state changed and wouldn’t be explicitly tied to the composition of that composable but that feels like the wrong approach. There is a code example in the thread.
Example with launched effect:
Copy code
fun NavGraphBuilder.home(
    navController: NavController
) {
    navigation(
        startDestination = Screen.Home.startDestination,
        route = Screen.Home.route
    ) {
        composable(route = Screen.Home.Dashboard.route) {
            val activeUser = LocalActiveUser.current // Maybe this should be from the VM too ¯\_(ツ)_/¯
            
            LaunchedEffect(activeUser) { if (activeUser?.isOnboarded != true) navController.navigate(Screen.Onboarding.route) }
            
            HomeScreen()
        }
    }
}
Example swapping nav destinations (this is definitely broken or at least my implementation of it is):
Copy code
@Composable
fun AppNavigation(
    modifier: Modifier = Modifier,
    navController: NavHostController
) {
    val activeUser = LocalActiveUser.current
    val startDestination = remember(activeUser) {
        if (activeUser?.isOnboarded == true) Screen.Home.route else Screen.Onboarding.route
    }

    AnimatedNavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {
        home(navController = navController)
        onboarding(navController = navController)
    }
}
I guess the further question would be if it is advisable to have separate navigation graphs for portions of the app and if so, what is the recommended approach for swapping between those graphs based on some state change? For example, if the user’s token expires then we would automatically send them to the login screen.
z
Nested, declarative navigation is super powerful. These two talks from droidcon NYC both talk about it a lot.
i
If you're using Navigation Compose, the NavController is the state holder for what screen you are on. That means that there's multiple options, of which you can pick whatever one works best for you: 1. Swap out the state outside of the NavHost (i.e., swap between showing your actual NavHost and some other content - a separate login screen, a whole other NavController+NavHost with its own graph, whatever. 2. Update the state of the NavController directly, as per your first code snippet to conditionally navigate to a different screen, thus keeping the NavController as your only source of truth for what screen you are actually on (this is more important if you are using that hoisted NavController to drive the visibility of bottom bars, etc.). 3. Swap out on the contents within an individual destination. Just because the NavController says you are at Screen A, doesn't mean that Screen A's content couldn't swap from one that represents the logged in state and another that represents the logged out state (and that logged out state could even be its own inner NavHost with its own back stack for complicated login processes) 4. Use a totally different approach to Navigation! There are a lot of Navigation frameworks that work in Compose; Jetpack Navigation Compose is just a tool in your toolbox.
p
I looked at the (second) talk @Zach Klippenstein (he/him) [MOD] mentioned above. It (Appyx) looks like a wonderful solution to my problem of needing to have nested navigation with multiple, different, bottom app bars. @Ian Lake do you have names or links for other libraries that I should look into? My initial research did not turn up anything at all, save maybe some useless Stack Overflow posts.
i
Decompose would be the other big one to look into: https://arkivanov.github.io/Decompose/
All three (Navigation Compose, Appyx, and Decompose) support infinite nesting, although they do it in different ways (and have different trade offs when it comes to how they work with deep links, etc)