简体   繁体   中英

Jetpack Compose Navigation: Direct navigation to route in a nested graph which is not startDestination

I am working on Jetpack Compose Navigation demo and I have a nested navigation graph with two different nested routes and screens for each nested route:

  • Login Graph
  • Main Graph

Login Graph has three routes for display three different Screens

  • Route "login" for displaying LoginScreen
  • Route "register" for displaying RegisterScreen
  • Route "recoverPassword" for displaying RecoverPasswordScreen

Main Graph has two routes for these screens

  • Route "home" for displaying HomeScreen
  • Route "settings" for displaying SettingsScreen

The nested graph creation is called in the MainActivity.kt

setContent {
        NavigationDemoTheme {

            val navController = rememberNavController()
            SetupNavGraph(navController = navController)
        }
    }

The function in the file NestedNavGraph.kt looks like this:

fun SetupNavGraph(navController: NavHostController) {
    NavHost(navController = navController, startDestination = "login_route")
    {
        loginGraph(navController = navController)
        mainGraph(navController = navController)
    }
}

In the file LoginNavGraph.kt I have defined the routes and start destination

fun NavGraphBuilder.loginGraph(navController: NavController) {
    navigation(startDestination = "login", route = "login_route") {
        composable(route = "login") {
            LoginScreen(navController = navController)
        }

        composable(route = "register") {
            RegisterScreen(navController = navController)
        }

        composable(route = "recover") {
            RecoverPasswordScreen(navController = navController)
        }
    }
}

In the file MainNavGraph.kt I have defined these two routes and this start destination:

 navigation(startDestination = "home", route = "main_route") {

        composable(route = "home") { 
            HomeScreen(navController = navController)
        }

        composable(route = "settings") { 
            SettingsScreen(navController = navController)
        }
    }

My questions now is: How can I display the RecoverPasswordScreen from SettingsScreen. I know I can navigate to the "login_route" from the SettingsScreen with but then the startDestination will be displayed, which is the LoginScreen.

// shows the LoginScreen because the startDestination in the "login_route" is set to "login"
navController.navigate(route = "login_route")
   

So, how can I directly navigate to the route "recover" in the nested graph route "login_route"? The following "workarounds" are in my mind:

Pass a parameter to the "login_route", for example something with:

navController.navigate(route = "login_route?destination=recover")

I will then have only a single route as a destination, for example "LoginView". This will change the loginGraph like this:

fun NavGraphBuilder.loginGraph(navController: NavController) {

    navigation(startDestination = "login_view, route = "login_route/{destination}) {

        composable(
            route = "login_view",
            arguments = listOf(
                navArgument("destination") { defaultValue = "login" },
            )
        ) { backStackEntry ->

            val destination =  backStackEntry.arguments?.getString("destination");

            destination?.let { destination ->  
                LoginView(destination = destination)
            }
        }
    }
}

The LoginView is composable whichw will have a own NavHost where I can set the startDestination with the query parameter from the previous route:

fun LoginView( destination : String = "login"){

    val navController = rememberNavController()
    var startDestination = destination;

    Scaffold ()
    {

        NavHost(
            navController = navController,
            startDestination = startDestination
        ) {

           composable(route = "login") {
             LoginScreen(navController = navController)
           }

           composable(route = "register") {
             RegisterScreen(navController = navController)
           }

           composable(route = "recover") {
             RecoverPasswordScreen(navController = navController) 
           }
    }
}

Now I should be able to call the RecoverPasswordScreen from the SettingsScreen with this:

navController.navigate(route = "login_route?destination=recover")

Another possibility is to have extra route for the RecoverPassword Screen in the MainGraph defined. Is there any other possibilty to directly acess a route in a nested graph? It would be great if could dynamically change startDestination when routing to "login_route" but I don't know how or if this is even possible.

Compose allows you to (Navigate with arguments) . This allows you to navigate to what you are calling "nested routes", that is a specific part within a screen.

Now, this is a simple explanation and I could leave you and have you figure it out. But I don't think this would be helpful to you as I think you have implemented your navigation in a difficult manner. Hence why trying to navigate is a bit more complex.

Here is a better way to implement it so that navigation like the one you want(RecoverPasswordScreen from Settings Screen) is easier.

Disclaimers

Change anything that's referred to as Main to your AppName.

I have not added all your screens

Main Screen class

//you could pass in parameters if needed into this constructor
enum class MainScreen(){
//these are your screens
   LogIn(),
   Settings(),
   Recover(),
   Home();

 companion object {
        fun fromRoute(route: String?): MainScreen =
            when (route?.substringBefore("/")) {
                LogIn.name -> LogIn
                Home.name -> Home
                Settings.name -> Settings
                Recover.name -> Recover
                //add the remaining screens
                // a null route resolves to LogInScreen.
                null -> LogIn
                else -> throw IllegalArgumentException("Route $route is not recognized.")
            }
    }

}

Main Activity Class

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MainApp()
        }
    }
}

@Composable
fun MainApp() {
    MainTheme {
        val allScreens = MainScreen.values().toList()
        val navController = rememberNavController()
        val backStackEntry = navController.currentBackStackEntryAsState()
        // currentScrren user is on good if app is large
        val currentScreen = MainScreen.fromRoute(
            backStackEntry.value?.destination?.route
        )
        //Using scaffold is a good idea
        Scaffold(
           //add topAppBar and all other things here
        ) { innerPadding ->
            MainNavHost(navController = navController, modifier = Modifier.padding(innerPadding))

        }
    }
}

//Scaffold requires innerPadding so remove if you decide not to use scaffold
@Composable
fun MainNavHost(navController: NavHostController, modifier: Modifier = Modifier) {
    NavHost(
        navController = navController,
        startDestination = LogIn.name,
        modifier = modifier
    ) {
        composable(LogIn.name) {
            /**
             Your body for logIn page
            **/

        }
//this is how you will navigate to Recover Screen from settings
        composable(Settings.name) {
            SettingsBody(onClickRecoverScreen = {navController.navigate(Recover.name)})

            }
        }
          composable(Recover.name) {
             /**
             Your body for Recover page
            **/
        }
        composable(Home.name) {
             /**
             Your body for Home page
            **/
        }
        


}


Settings Screen

@Composable
fun SettingsBody(
    //this callback is how you will navigate from Settings to RecoverPassword
    onClickRecoverScreen: () -> Unit = {},
) {
    Column(
       //Add your designs for this screen
    ) {
        Button(onClick = {onClickRecoverScreen})
    }
}

This is the simplest way (in my opinion) to implement Navigation as you can simply add callbacks to navigate to different places in the app and it is much more testable(if you test;) ) and scalable. You can also add deep links and use arguments (as mentioned above) to navigate to specific parts of the app (eg, a specific account in an Accounts Screen)

I highly recommend this Navigation Codelab if you want to understand more.

A possible solution is to use deeplinks defined in the navigation graph - they also work for nested destinations. Then, instead of navigating to the route name, you can use navController.navigate(deepLinkUri)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM