kotlin Jetpack组合导航:直接导航到嵌套图中不是startDestination的路径

mrphzbgm  于 2023-03-24  发布在  Kotlin
关注(0)|答案(3)|浏览(186)

我正在Jetpack编写导航演示,我有一个嵌套导航图,其中包含两个不同的嵌套路线和每个嵌套路线的屏幕:

  • 登录图
  • 主图

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

  • 显示登录屏幕的路径“登录”
  • 用于显示RegisterScreen的路由“register”
  • 用于显示RecoverPasswordScreen的路由“recoverPassword”

主图为这些屏幕提供了两条路径

  • 路由“home”以显示主屏幕
  • 用于显示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)
        }
    }

我现在的问题是如何从设置屏幕显示RecoverPasswordScreen?我知道我可以使用从设置屏幕导航到“login_route”,但随后将显示startDestination,即登录屏幕。

// 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”。这将像这样更改loginGraph:

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中为RecoverPassword屏幕定义额外的路由。是否有其他可能性直接访问嵌套图中的路由?如果可以在路由到“login_route”时动态更改startDestination就太好了,但我不知道如何或是否可能。

jslywgbw

jslywgbw1#

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

这是实现导航的最简单的方法(在我看来),因为你可以简单地添加回调来导航到应用程序中的不同位置,而且它更可测试(如果你测试;您还可以添加深层链接并使用参数(如上所述)导航到应用程序的特定部分(例如,帐户屏幕中的特定帐户)
如果你想了解更多,我强烈推荐这款Navigation Codelab

q9yhzks0

q9yhzks02#

一个可能的解决方案是使用导航图中定义的深度链接-它们也适用于嵌套目的地。

li9yvcax

li9yvcax3#

如果一个屏幕级的组合在我的应用程序中的不同地方被重用,我倾向于给予它一个自己的导航图,即使它是那个图中唯一的屏幕?
例如,在您的特定场景中,您将有“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()-可以只是添加到这个新的图形。

相关问题