Jetpack Compose Navigation:直接导航到非 startDestination 的嵌套图中的路由

发布于 2025-01-12 09:14:52 字数 4028 浏览 1 评论 0原文

我正在开发 Jetpack Compose Navigation 演示,我有一个嵌套导航图,其中有两个不同的嵌套路由和每个嵌套路由的屏幕:

  • 登录图
  • 主图

登录图具有三个路由,用于显示三个不同的屏幕

  • 路由“登录”用于显示 LoginScreen
  • 路由“ register”用于显示 RegisterScreen
  • 路由“recoverPassword”用于显示 RecoverPasswordScreen

主图对于这些屏幕有两个路由

  • 路由“home”用于显示 HomeScreen
  • 路由“settings”用于显示 SettingsScreen

嵌套图创建在 MainActivity.kt 中调用。

setContent {
        NavigationDemoTheme {

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

文件 NestedNavGraph.kt 中的函数如下所示:

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

在文件 LoginNavGraph.kt 中,我定义了路线和起始目的地

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)
        }
    }
}

在文件 MainNavGraph.kt 中,我定义了这两条路线和此起始目的地:

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

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

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

我现在的问题是:如何从 SettingsScreen 显示 RecoverPasswordScreen。我知道我可以从 SettingsScreen 导航到“login_route”,但随后将显示 startDestination,即 LoginScreen。

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

那么,如何直接导航到嵌套图路由“login_route”中的路由“recover”?我想到了以下“解决方法”:

将参数传递给“login_route”,例如:

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

然后我将只有一个路由作为目的地,例如“LoginView”。这将像这样改变登录图:

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)
            }
        }
    }
}

LoginView 是可组合的,它将有一个自己的 NavHost,我可以在其中使用上一个路由中的查询参数设置 startDestination:

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) 
           }
    }
}

现在我应该能够从 SettingsScreen 中调用 RecoverPasswordScreen,方法是:

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

另一种可能性是在定义的 MainGraph 中为恢复密码屏幕提供额外的路由。是否有其他可能性可以直接访问嵌套图中的路线?如果可以在路由到“login_route”时动态更改 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

救赎№ 2025-01-19 09:14:52

一种可能的解决方案是使用导航图中定义的深层链接 - 它们也适用于嵌套目的地。然后,您可以使用 navController.navigate(deepLinkUri) 而不是导航到路由名称

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)

这个俗人 2025-01-19 09:14:52

如果屏幕级可组合项在我的应用程序中的不同位置重复使用,我倾向于只给它自己的导航图,即使它是该图中唯一的屏幕?

例如,在您的特定场景中,您将拥有“loginGraph”、“mainGraph”,然后是“recoverPassword”图表,其中“recoverPassword”图表中的唯一目标是 RecoverPasswordScreen

这将使您能够从应用程序中的任何位置导航到 RecoverPasswordScreen,同时仍为大多数流程保留单独的导航图。

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

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

fun NavGraphBuilder.recoverPasswordGraph(navController: NavController) {
    navigation(
        startDestination = "recoverPassword",
        route = "recover_password_route"
    ) {
        composable(route = "recoverPassword") {
            RecoverPasswordScreen()
        }
    }
}

这也很好用,因为您稍后可能有更多屏幕要添加到恢复密码流程中 - 例如 PasswordRecoverySuccessfulScreen() - 并且可以将其添加到这个新图表中。

If a screen-level composable is re-used in very different places in my applications, I tend to just give it its own navigation graph, even if it is the only screen inside that graph?

For example, in your specific scenario, you would have your "loginGraph", "mainGraph" and then a "recoverPassword" graph, where the only destination in the "recoverPassword" graph is the RecoverPasswordScreen.

This will enable you to navigate to the RecoverPasswordScreen from anywhere in your application, whilst still keeping separate nav graphs for most of your flows.

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

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

fun NavGraphBuilder.recoverPasswordGraph(navController: NavController) {
    navigation(
        startDestination = "recoverPassword",
        route = "recover_password_route"
    ) {
        composable(route = "recoverPassword") {
            RecoverPasswordScreen()
        }
    }
}

This also works nicely as you might have more screens to add to the recover password flow later - e.g. a PasswordRecoverySuccessfulScreen() - and that can just be added to this new graph.

智商已欠费 2025-01-19 09:14:52

Compose 允许您(使用参数导航)。这允许您导航到所谓的“嵌套路由”,即屏幕中的特定部分。

现在,这是一个简单的解释,我可以离开你并让你弄清楚。但我认为这对您没有帮助,因为我认为您已经以困难的方式实现了导航。因此,为什么尝试导航会更复杂一些。

这是一种更好的实现方法,以便您想要的导航(从设置屏幕恢复密码屏幕)更容易。

免责声明

Main 中的任何内容更改为您的 AppName。

我还没有添加您的所有屏幕

主屏幕类

//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.")
            }
    }

}

主活动类

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
            **/
        }
        


}


设置屏幕

@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})
    }
}

这是实现导航的最简单方法(在我看来),因为您只需添加回调即可导航到应用程序中的不同位置,它更具可测试性(如果您测试;))和可扩展性。您还可以添加深层链接并使用参数(如上所述)导航到应用程序的特定部分(例如,帐户屏幕中的特定帐户)

我强烈推荐此导航代码实验室

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 (e.g., a specific account in an Accounts Screen)

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文