Jetpack Compose导航Kotlin DSL传递自定义对象类型安全异常排查
问题
我正在用Jetpack Compose Navigation的Kotlin DSL实现类型安全导航,但遇到了问题。相关代码如下:
页面路由定义
@Serializable data object SelectMenuNav @Serializable data class SelectProductNav( val menuSelection: MenuSelection ) @Serializable data class ProductDetailsNav( val menuSelection: MenuSelection, val product: Product )
自定义导航类型(传递Product对象)
object CustomNavType { val ProductType = object : NavType<Product>( isNullableAllowed = false ) { override fun get(bundle: Bundle, key: String): Product? { return Json.decodeFromString(bundle.getString(key) ?: return null) } override fun parseValue(value: String): Product { return Json.decodeFromString(Uri.decode(value)) } override fun put(bundle: Bundle, key: String, value: Product) { bundle.putString(key, Json.encodeToString(value)) } override fun serializeAsValue(value: Product): String { return Uri.encode(Json.encodeToString(value)) } } }
应用导航实现
@Composable fun AppNavigation(modifier: Modifier = Modifier) { val navController = rememberNavController() NavHost( navController = navController, startDestination = SelectMenuNav ) { composable<SelectMenuNav> { ChooseMenuScreen(onSelection = { menuSelection -> navController.navigate( SelectProductNav( menuSelection = menuSelection ) ) }) } composable<SelectProductNav>( typeMap = mapOf( typeOf<MenuSelection>() to NavType.EnumType(MenuSelection::class.java) ) ) { val menuSelection = it.toRoute<SelectProductNav>().menuSelection ChooseProductScreen( menuSelection = menuSelection, viewModel = hiltViewModel(), onNavigateToProductDetails = { product -> navController.navigate( ProductDetailsNav( product = product, menuSelection = menuSelection ) ) }) } composable<ProductDetailsNav>( typeMap = mapOf( typeOf<Product>() to CustomNavType.ProductType, typeOf<MenuSelection>() to NavType.EnumType(MenuSelection::class.java) ) ) { backStackEntry -> Log.d("AppNavigation", "ProductDetailsNav: ${backStackEntry.toRoute<ProductDetailsNav>()}") ProductDetailsScreen( modifier = Modifier, vm = hiltViewModel(), navigateToEditor = {}, navigateBack = {} ) } } }
日志显示Product对象已成功传递:
D ProductDetailsNav: ProductDetailsNav(menuSelection=NAVA, product=Product(id=4371, productName=[...]
但ViewModel抛出错误:
java.lang.IllegalArgumentException: Route app.xml.aionianew.navigation.ProductDetailsNav could not find any NavType for argument product of type app.xml.aionianew.database.internal.data.Product - typeMap received was {} at androidx.navigation.serialization.RouteSerializerKt$generateNavArguments$2$1.invoke(RouteSerializer.kt:108) at androidx.navigation.serialization.RouteSerializerKt$generateNavArguments$2$1.invoke(RouteSerializer.kt:103) at androidx.navigation.NamedNavArgumentKt.navArgument(NamedNavArgument.kt:21) at androidx.navigation.serialization.RouteSerializerKt.generateNavArguments(RouteSerializer.kt:103) at androidx.navigation.SavedStateHandleKt.internalToRoute(SavedStateHandle.kt:50) at app.xml.aionianew.screens.product_details.ViewModelProductDetails.<init>(ViewModelProductDetails.kt:1018) at app.xml.aionianew.DaggerMyApp_HiltComponents_SingletonC$ViewModelCImpl$SwitchingProvider.get(DaggerMyApp_HiltComponents_SingletonC.java:497) at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory$2.createViewModel(HiltViewModelFactory.java:133)
ViewModel代码:
@HiltViewModel class ViewModelProductDetails @Inject constructor( private val uc: UseCasesProductDetails, savedState: SavedStateHandle ) : ViewModel() { private val selectedProduct: Product = savedState.toRoute<ProductDetailsNav>().product private val menuSelection:MenuSelection = savedState.toRoute<ProductDetailsNav>().menuSelection }
请问我遗漏了什么配置?
解决方案
问题出在ViewModel中调用savedState.toRoute<ProductDetailsNav>()时,没有传入对应的typeMap参数。你在NavHost的composable块里配置的typeMap只作用于Composable函数内的backStackEntry.toRoute()调用,ViewModel里的SavedStateHandle并不知道这个配置。
有两种解决方式:
方式一:调用toRoute时传入typeMap
修改ViewModel代码,在调用toRoute时手动传入对应的类型映射:
@HiltViewModel class ViewModelProductDetails @Inject constructor( private val uc: UseCasesProductDetails, savedState: SavedStateHandle ) : ViewModel() { private val typeMap = mapOf( typeOf<Product>() to CustomNavType.ProductType, typeOf<MenuSelection>() to NavType.EnumType(MenuSelection::class.java) ) private val route = savedState.toRoute<ProductDetailsNav>(typeMap = typeMap) private val selectedProduct: Product = route.product private val menuSelection: MenuSelection = route.menuSelection }
方式二:全局注册自定义NavType
如果不想在每个ViewModel里重复传入typeMap,可以全局注册自定义类型,这样toRoute会自动使用全局配置:
- 定义全局类型映射:
object GlobalNavTypeMap { val globalTypeMap = mapOf( typeOf<Product>() to CustomNavType.ProductType, typeOf<MenuSelection>() to NavType.EnumType(MenuSelection::class.java) ) }
- 在Application子类的
onCreate方法中注册全局配置:
class MyApp : Application() { override fun onCreate() { super.onCreate() RouteSerializer.registerTypeMap(GlobalNavTypeMap.globalTypeMap) } }
之后ViewModel里的savedState.toRoute<ProductDetailsNav>()就会自动使用全局配置,无需手动传入typeMap。
注意:全局注册的方式需要确保所有自定义类型都提前注册,避免遗漏导致类似错误。
内容的提问来源于stack exchange,提问作者F.Mysir
相关产品推荐
相关产品推荐

