Compose NavHost传含#参数时SavedStateHandle仅存部分内容的解决方法
问题出在#是URI的片段标识符,当你直接把带#的参数拼到导航路由字符串里时,NavController会将#后面的内容解析为路由的片段部分,而非参数的一部分,导致SavedStateHandle只能获取到#之前的内容。
以下是两种无需逐个替换特殊字符的解决方案:
方案一:URI编码解码(适配Deep Link场景)
对包含特殊字符的参数进行URI编码,将特殊字符转换为URI安全的格式(比如#转为%23),接收时再解码恢复原字符串。这种方式适合需要将参数暴露在路由中的场景(比如Deep Link)。
修改跳转代码:
Button(modifier = Modifier.wrapContentHeight(), onClick = { val encodedPlayerTag = Uri.encode("name#123") navController.navigate("$PLAYER_STATS/$encodedPlayerTag") }) { Text(text = "跳转到下一页并传递参数") }
修改ViewModel解码逻辑:
@HiltViewModel class PlayerStatsScreenViewModel @Inject constructor( savedStateHandle: SavedStateHandle, ) : ViewModel() { val userId: String = Uri.decode(checkNotNull(savedStateHandle[PLAYER_TAG])) }
方案二:Bundle传递参数(推荐内部导航使用)
直接通过Bundle传递参数,而非拼接在路由字符串中。这种方式不需要处理任何特殊字符,因为参数是通过Bundle直接传递,不经过URI解析流程,更适合App内部导航场景。
修改跳转代码:
Button(modifier = Modifier.wrapContentHeight(), onClick = { navController.navigate(PLAYER_STATS) { arguments = bundleOf(PLAYER_TAG to "name#123") } }) { Text(text = "跳转到下一页并传递参数") }
修改NavHost路由定义:
composable( PLAYER_STATS, arguments = listOf(navArgument(PLAYER_TAG) { type = NavType.StringType nullable = false // 标记参数为必填 }), ) { val viewModel: PlayerStatsScreenViewModel = hiltViewModel() Text(text = viewModel.userId) }
ViewModel的代码无需修改,SavedStateHandle依然能正常获取完整参数值。
完整代码示例(方案二)
@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { OversearchTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { App() } } } } } @Composable fun App() { val navController = rememberNavController() Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { NavHost(navController = navController, startDestination = HOME) { composable(HOME) { Button(modifier = Modifier.wrapContentHeight(), onClick = { navController.navigate(PLAYER_STATS) { arguments = bundleOf(PLAYER_TAG to "name#123") } }) { Text(text = "跳转到下一页并传递参数") } } composable( PLAYER_STATS, arguments = listOf(navArgument(PLAYER_TAG) { type = NavType.StringType nullable = false }), ) { val viewModel: PlayerStatsScreenViewModel = hiltViewModel() Text(text = viewModel.userId) } } } } @HiltViewModel class PlayerStatsScreenViewModel @Inject constructor( savedStateHandle: SavedStateHandle, ) : ViewModel() { val userId: String = checkNotNull(savedStateHandle[PLAYER_TAG]) } const val HOME = "home" const val PLAYER_STATS = "playerStats" const val PLAYER_TAG = "playerTag"
内容的提问来源于stack exchange,提问作者George Shalvashvili
相关产品推荐
相关产品推荐

