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

如何通过Navigation Compose将Activity的onNewIntent()数据传递给当前页面的ViewModel?

如何通过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数据。

实现步骤:

  1. 在Activity中持有NavHostController:因为我们需要在onNewIntent()里拿到当前显示页面的BackStackEntry,所以要在创建NavHost时把NavHostController存起来。
  2. 处理onNewIntent()和初始Intent:在这两个地方解析Deep Link数据,然后把数据存入当前页面的SavedStateHandle。
  3. 在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订阅事件。

实现步骤:

  1. 创建全局EventBus:用单例模式封装一个MutableSharedFlow,用来发送和接收Deep Link事件。
  2. 在Activity中发送事件:解析Deep Link数据后,通过EventBus发送事件。
  3. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 07:58:12