Compose Navigation v2.8.x 拯救者一般的改动。

1,843 阅读2分钟

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

  //.......
}

结束

至此全文结束,本人知识有限,如有描述错误之处,愿纠正。👍👍👍👍👍👍👍