Compose Navigation v2.8.x 拯救者一般的改动。
回顾v2.8.0以前(噩梦一般的参数定义)
v2.8.0以前的api 路由和定义和传参数真的很痛苦。上个例子感受一下,还容易出错。 我们来对比一下,改动前和优化后的区别。
定义一个路由
旧的api
composable(
"${RouteConfig.ROUTE_PAGETWO}/{${ParamsConfig.PARAMS_NAME}}/{${ParamsConfig.PARAMS_AGE}}",
arguments = listOf(
navArgument("$ParamsConfig.PARAMS_NAME") {},
navArgument("$ParamsConfig.PARAMS_AGE") { type = NavType.IntType }
)
) {
PageTwo(navController)
}
新的api
@Serializable
data class Profile(val name: String, val age: Int)
composable<Profile> { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile)
}
跳转到页面的代码
旧的api
navController.navigate("${RouteConfig.ROUTE_PAGETWO}/kang/18")
新的api
navController.navigate(Profile("kang", 18))
经过上面的对比我们很明显看的出来:
1,定义路由和跳转变的简单了
2,类型安全和对象的加入,使得传参类型不会出错。
3,获取参数以对象的方式获取,也变得简单。没有以前繁琐。
来,让我们开启新的旅程,下面是接入方式。
声明依赖项
在应用或模块的 build.gradle 文件中添加所需制品的依赖项:
dependencies {
val nav_version = "2.8.3"
// Jetpack Compose integration
implementation("androidx.navigation:navigation-compose:$nav_version")
//序列化
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}
序列化插件
请在顶层 build.gradle 文件中包含以下 classpath:
注意: kotlin("plugin.serialization") version "1.9.24" 这里的version得和你的kotlin版本对应上。
buildscript {
plugins {
//添加序列化插件,如果已经有了,就不需要添加了。
kotlin("jvm") version "1.9.24"
kotlin("plugin.serialization") version "1.9.24"
}
}
请将以下行添加到应用或模块的 build.gradle 文件中:
plugins {
//序列化
id("org.jetbrains.kotlin.plugin.serialization")
}
定义导航和类型
定义页面和接收的参数类型
@Serializable
object Home
// 带一个参数
@Serializable
data class Profile(val name: String,val age:Int)
@Composable
fun ProfileScreen(profile: Profile) {
Text(
text = "Hello ${profile.name}!",
)
}
@Composable
fun HomeScreen(onNavigateToProfile: () -> Unit) {
Text(
text = "Hello HomeScreen!",
modifier = Modifier.padding(20.dp).clickable { onNavigateToProfile.invoke() }
)
}
定义页面路由
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Home,
) {
composable<Home> {
HomeScreen {
navController.navigate(Profile("kang",18))
}
}
composable<Profile> { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile)
}
}
好啦!!现在你定义页面和传递参数变得,像喝水一样简单了,再也不用被旧的api路由定义折磨。
简单看看 backStackEntry.toRoute() 的实现
我们可以看的出来,还是从 arguments 取出所有参数,进行序列化成对象的。
public inline fun <reified T> NavBackStackEntry.toRoute(): T {
val bundle = arguments ?: Bundle()
val typeMap = destination.arguments.mapValues { it.value.type }
return serializer<T>().decodeArguments(bundle, typeMap)
}
RouteDecoder 中的源码,我重点关注 get(key: String) 方法,可以看得出来还是 bundle 取出数据。
private class SavedStateArgStore(
private val handle: SavedStateHandle,
private val typeMap: Map<String, NavType<*>>
) : ArgStore() {
override fun get(key: String): Any? {
val arg: Any? = handle[key]
val bundle = bundleOf(key to arg)
return checkNotNull(typeMap[key]) { "Failed to find type for $key when decoding $handle" }[
bundle, key]
}
override fun contains(key: String) = handle.contains(key)
}
private class BundleArgStore(
private val bundle: Bundle,
private val typeMap: Map<String, NavType<*>>
) : ArgStore() {
override fun get(key: String): Any? {
val navType = typeMap[key]
return navType?.get(bundle, key)
}
override fun contains(key: String) = bundle.containsKey(key)
}
升级新版本建议
so,如果你ViewModel有以下这样的代码,也可以一步步过渡过来。只要参数定义一致即可。
class MyViewModel(state: SavedStateHandle) : ViewModel() {
private val name = state.get<String?>("name")
//.......
}
结束
至此全文结束,本人知识有限,如有描述错误之处,愿纠正。👍👍👍👍👍👍👍