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

Jetpack Compose中使用ViewModel实现startActivity的最佳实践

在Compose中通过ViewModel启动Activity的正确实现方式

ViewModel的核心职责是处理业务逻辑,不应该直接持有Context或执行页面跳转操作,否则会破坏关注点分离原则,还可能导致内存泄漏。正确的做法是让ViewModel通过事件通知的方式,触发UI层(Composable)执行跳转。下面是几种常用的实现方案:

方案1:使用StateFlow发送跳转事件

这是最通用的方案,通过StateFlow传递跳转信号,Composable收集信号后执行跳转:

修改ViewModel代码

class SomeViewModel : ViewModel() {
    private val _text = mutableStateOf("Test")
    val text: String
        get() = _text.value

    // 用于触发跳转的StateFlow,用Boolean标记事件状态
    private val _shouldNavigate = MutableStateFlow(false)
    val shouldNavigate = _shouldNavigate.asStateFlow()

    fun onClick() {
        if (text.isEmpty()) {
            // 发送跳转事件
            _shouldNavigate.value = true
        } else {
            _text.value = ""
        }
    }

    // 重置事件状态,避免配置变化后重复触发
    fun onNavigateFinished() {
        _shouldNavigate.value = false
    }
}

修改Composable代码

@Composable
fun TextExample(model: SomeViewModel = viewModel()) {
    val context = LocalContext.current
    // 收集跳转事件
    LaunchedEffect(model.shouldNavigate) {
        if (model.shouldNavigate.value) {
            // 启动目标Activity
            val intent = Intent(context, AnotherActivity::class.java)
            context.startActivity(intent)
            // 通知ViewModel事件已处理
            model.onNavigateFinished()
        }
    }

    TextButton(onClick = { model.onClick() }) {
        Text(text = model.text)
    }
}

方案2:使用Compose Navigation组件(推荐)

如果项目已经集成Jetpack Compose Navigation,直接通过导航路由跳转更符合架构规范:

修改ViewModel代码

class SomeViewModel : ViewModel() {
    private val _text = mutableStateOf("Test")
    val text: String
        get() = _text.value

    // 用SharedFlow发送导航事件,支持传递路由参数
    private val _navigationEvent = MutableSharedFlow<String>()
    val navigationEvent = _navigationEvent.asSharedFlow()

    fun onClick() {
        if (text.isEmpty()) {
            viewModelScope.launch {
                // 发送目标路由
                _navigationEvent.emit("another_screen")
            }
        } else {
            _text.value = ""
        }
    }
}

修改Composable代码

@Composable
fun TextExample(navController: NavController, model: SomeViewModel = viewModel()) {
    // 收集导航事件并执行跳转
    LaunchedEffect(Unit) {
        model.navigationEvent.collect { route ->
            when (route) {
                "another_screen" -> navController.navigate(route)
            }
        }
    }

    TextButton(onClick = { model.onClick() }) {
        Text(text = model.text)
    }
}

方案3:使用回调接口(简单场景)

如果项目规模较小,也可以通过回调接口实现通知:

修改ViewModel代码

class SomeViewModel : ViewModel() {
    private val _text = mutableStateOf("Test")
    val text: String
        get() = _text.value

    // 定义跳转回调
    var onNavigateRequest: (() -> Unit)? = null

    fun onClick() {
        if (text.isEmpty()) {
            onNavigateRequest?.invoke()
        } else {
            _text.value = ""
        }
    }
}

修改Composable代码

@Composable
fun TextExample(model: SomeViewModel = viewModel()) {
    val context = LocalContext.current

    // 初始化并清理回调,避免内存泄漏
    DisposableEffect(Unit) {
        model.onNavigateRequest = {
            val intent = Intent(context, AnotherActivity::class.java)
            context.startActivity(intent)
        }
        onDispose {
            model.onNavigateRequest = null
        }
    }

    TextButton(onClick = { model.onClick() }) {
        Text(text = model.text)
    }
}

关键注意事项

  • 禁止在ViewModel中直接持有Context或调用startActivity,否则会因ViewModel生命周期长于Activity导致内存泄漏。
  • 使用Flow方案时,必须处理事件重置(如方案1的onNavigateFinished),防止屏幕旋转等配置变化后重复触发跳转。
  • 若项目使用Compose Navigation,优先选择方案2,统一路由管理更便于维护。

内容的提问来源于stack exchange,提问作者Christina

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 11:10:42