You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Compose NavHost传含#参数时SavedStateHandle仅存部分内容的解决方法

解决NavHost传递含特殊字符参数被截断的问题

问题出在#是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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 01:18:14