如何通过Navigation Compose将Activity的onNewIntent()数据传递给当前页面的ViewModel?
刚好我之前做Jetpack Compose项目时也碰到过一模一样的场景——用Chrome Custom Tab做SSO登录后,要把Deep Link返回的数据传给当前显示页面的ViewModel,而且得是通用方案,不管当前是哪个页面。这里给你两个实用的方案,其中第一个是我最推荐的,完全贴合Navigation Compose的设计思路:
方案一:利用Navigation的SavedStateHandle(推荐)
Navigation Compose的每个页面(Destination)都对应一个BackStackEntry,而每个BackStackEntry都自带SavedStateHandle——这是Jetpack官方用来在组件间传递生命周期感知状态的工具,非常适合用来传递Deep Link数据。
实现步骤:
- 在Activity中持有NavHostController:因为我们需要在
onNewIntent()里拿到当前显示页面的BackStackEntry,所以要在创建NavHost时把NavHostController存起来。 - 处理onNewIntent()和初始Intent:在这两个地方解析Deep Link数据,然后把数据存入当前页面的
SavedStateHandle。 - 在ViewModel中监听SavedStateHandle的数据:ViewModel通过构造函数接收
SavedStateHandle,然后监听数据变化并处理业务逻辑。
代码示例:
主Activity代码:
class MainActivity : ComponentActivity() { private lateinit var navController: NavHostController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { YourAppTheme { // 保存NavHostController实例 navController = rememberNavController() NavHost(navController = navController, startDestination = "login") { composable("login") { LoginScreen(viewModel = hiltViewModel()) } // 其他页面的导航配置 } } } // 处理应用启动时的初始Deep Link Intent intent?.let { handleDeepLink(it) } } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) // 处理后续的Deep Link Intent(比如SSO登录返回) intent?.let { handleDeepLink(it) } } private fun handleDeepLink(intent: Intent) { val deepLinkUri = intent.data ?: return // 解析Deep Link中的数据(比如token、用户信息) val deepLinkData = parseDeepLink(deepLinkUri) // 获取当前显示页面的BackStackEntry navController.currentBackStackEntry?.let { currentEntry -> // 将数据存入SavedStateHandle currentEntry.savedStateHandle["DEEP_LINK_LOGIN_DATA"] = deepLinkData } } // 自定义解析逻辑,根据你的Deep Link格式调整 private fun parseDeepLink(uri: Uri): LoginDeepLinkData { val authToken = uri.getQueryParameter("auth_token") ?: "" val userId = uri.getQueryParameter("user_id") ?: "" return LoginDeepLinkData(authToken, userId) } } // 封装Deep Link数据的数据类 data class LoginDeepLinkData(val authToken: String, val userId: String)
ViewModel代码:
class LoginViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle ) : ViewModel() { // 用StateFlow监听SavedStateHandle中的数据变化 private val _deepLinkLoginData = savedStateHandle.getStateFlow<LoginDeepLinkData?>( key = "DEEP_LINK_LOGIN_DATA", initialValue = null ) val deepLinkLoginData = _deepLinkLoginData.asStateFlow() init { viewModelScope.launch { deepLinkLoginData.collect { data -> data?.let { // 处理登录成功逻辑:保存token、跳转主页等 processSuccessfulLogin(it.authToken, it.userId) // 处理完后清空数据,避免重复触发 savedStateHandle["DEEP_LINK_LOGIN_DATA"] = null } } } } private fun processSuccessfulLogin(token: String, userId: String) { // 这里写你的业务逻辑,比如存入DataStore、请求用户信息等 } }
这个方案的好处是:数据和页面的生命周期完全绑定,只有当前显示的页面的ViewModel能拿到数据,不会出现无关页面接收到事件的情况,也不用担心内存泄漏。
方案二:用Kotlin Flow实现全局事件总线
如果你的场景更复杂(比如多个页面都需要监听Deep Link事件),可以用Kotlin的SharedFlow实现一个全局事件总线,让需要的ViewModel订阅事件。
实现步骤:
- 创建全局EventBus:用单例模式封装一个
MutableSharedFlow,用来发送和接收Deep Link事件。 - 在Activity中发送事件:解析Deep Link数据后,通过EventBus发送事件。
- 在ViewModel中订阅事件:ViewModel在初始化时订阅EventBus的事件,处理业务逻辑。
代码示例:
全局EventBus:
object DeepLinkEventBus { private val _deepLinkEvents = MutableSharedFlow<LoginDeepLinkData>() // 对外暴露只读的Flow val deepLinkEvents = _deepLinkEvents.asSharedFlow() // 发送事件的方法 suspend fun sendDeepLinkEvent(data: LoginDeepLinkData) { _deepLinkEvents.emit(data) } }
Activity中修改handleDeepLink方法:
private fun handleDeepLink(intent: Intent) { val deepLinkUri = intent.data ?: return val deepLinkData = parseDeepLink(deepLinkUri) // 通过EventBus发送事件 lifecycleScope.launch { DeepLinkEventBus.sendDeepLinkEvent(deepLinkData) } }
ViewModel中订阅事件:
class LoginViewModel : ViewModel() { init { viewModelScope.launch { DeepLinkEventBus.deepLinkEvents.collect { data -> // 处理登录逻辑 processSuccessfulLogin(data.authToken, data.userId) } } } // ... 其他逻辑 }
这个方案的缺点是:所有订阅了事件的ViewModel都会收到数据,所以你需要额外加判断逻辑(比如结合Navigation的当前目的地ID),确保只有当前显示的页面处理事件,否则可能会出现误触发的情况。
备注:内容来源于stack exchange,提问作者Roberto Leinardi
相关产品推荐
相关产品推荐

