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

Android Jetpack Compose+Hilt下ViewModel间传值的最佳实践

问题

我正在使用Jetpack Compose和Hilt构建Android应用,遵循MVVM模式。现有两个Composable函数:

@Composable
fun TimerView(
    viewModel: TimerViewModel = hiltViewModel(),
) {...}

和

@Composable
fun SensorScreen(viewModel: SensorViewModel = hiltViewModel()) {
    val values = viewModel.recentValues.mapValues { it.value.collectAsState(listOf(0f)) }
    Column(
        modifier = Modifier.verticalScroll(rememberScrollState()),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
            TimerView()
        // 其他代码
    }
}

其中TimerView嵌套在SensorScreen中,二者各有独立的ViewModel。TimerViewModel代码如下:

@HiltViewModel
class TimerViewModel @Inject constructor(
) : ViewModel() {
    val totalTime = 100
    private val _time = MutableStateFlow(totalTime)
    val time = _time.asStateFlow()
    init {
        viewModelScope.launch {
            while (true) {
                delay(1000)
                _time.value = Integer.max(0, _time.value - 1)
                if (_time.value == 0) {
                    onTimeFinished()
                    break
                }
            }
        }
    }

    // 假设存在的计时结束处理方法
    private fun onTimeFinished() {
        // 处理逻辑
    }
}

我希望从父ViewModel(SensorViewModel)传递totalTime值给TimerViewModel,请问如何做才符合最佳实践和MVVM模式?

解决方案

要实现从SensorViewModel向TimerViewModel传递动态totalTime,同时遵循MVVM和Hilt的最佳实践,**Hilt辅助注入(Assisted Injection)**是官方推荐的方案,它能妥善处理ViewModel的动态构造参数,同时保证生命周期安全。

步骤1:添加必要依赖

确保项目中引入Hilt辅助注入相关依赖:

implementation "androidx.hilt:hilt-navigation-compose:1.1.0"
implementation "com.google.dagger:hilt-android:2.48"
kapt "com.google.dagger:hilt-android-compiler:2.48"
implementation "androidx.hilt:hilt-assisted:1.1.0"
kapt "androidx.hilt:hilt-compiler:1.1.0"

步骤2:改造TimerViewModel

使用@AssistedInject和@Assisted注解声明动态参数,并创建对应的辅助工厂和ViewModel Provider:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class TimerViewModel @AssistedInject constructor(
    @Assisted private val totalTime: Int // 动态传入的总时长
) : ViewModel() {
    private val _time = MutableStateFlow(totalTime)
    val time = _time.asStateFlow()

    init {
        startTimer()
    }

    private fun startTimer() {
        viewModelScope.launch {
            while (true) {
                delay(1000)
                _time.value = Integer.max(0, _time.value - 1)
                if (_time.value == 0) {
                    onTimeFinished()
                    break
                }
            }
        }
    }

    private fun onTimeFinished() {
        // 计时结束的处理逻辑
    }

    // 定义辅助工厂,用于创建ViewModel实例
    @dagger.assisted.AssistedFactory
    interface Factory {
        fun create(totalTime: Int): TimerViewModel
    }

    // 提供ViewModelProvider.Factory,供Compose调用
    companion object {
        fun provideFactory(
            factory: Factory,
            totalTime: Int
        ): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
                return factory.create(totalTime) as T
            }
        }
    }
}

步骤3:在Composable中传递参数并创建ViewModel

修改TimerView使其接收totalTime参数,通过辅助工厂创建ViewModel;同时在SensorScreen中传递来自SensorViewModel的totalTime值:

@Composable
fun TimerView(
    totalTime: Int,
    viewModel: TimerViewModel = hiltViewModel(
        factory = TimerViewModel.provideFactory(
            hiltViewModel<TimerViewModel.Factory>(),
            totalTime = totalTime
        )
    )
) {
    // TimerView的UI逻辑,例如展示viewModel.time的状态
}

@Composable
fun SensorScreen(viewModel: SensorViewModel = hiltViewModel()) {
    val values = viewModel.recentValues.mapValues { it.value.collectAsState(listOf(0f)) }
    // 从SensorViewModel获取需要传递的totalTime
    val timerTotalTime = viewModel.timerTotalTime // 替换为你实际的属性名

    Column(
        modifier = Modifier.verticalScroll(rememberScrollState()),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        TimerView(totalTime = timerTotalTime)
        // 其他UI代码
    }
}

方案优势

  • 符合MVVM原则:ViewModel的所有依赖(包括动态参数)在创建时注入,保证了ViewModel的独立性和可测试性——单元测试中可轻松传入不同的totalTime值进行验证。
  • 遵循Hilt设计规范:辅助注入是Hilt官方针对动态ViewModel参数的解决方案,避免了手动管理ViewModel生命周期的风险。
  • Composable简洁性:通过hiltViewModel函数绑定生命周期,无需手动处理配置变更等场景下的ViewModel重建问题。

备选方案(仅适用于简单场景)

如果场景非常简单,也可以在TimerViewModel中添加初始化方法,在Composable中传递参数后调用,但这种方案不符合ViewModel状态初始化的最佳实践,可能导致状态不一致:

// TimerViewModel添加方法
fun initTimer(newTotalTime: Int) {
    _time.value = newTotalTime
    // 若之前计时已启动,需先取消再重启
}

// TimerView实现
@Composable
fun TimerView(
    totalTime: Int,
    viewModel: TimerViewModel = hiltViewModel(),
) {
    LaunchedEffect(totalTime) {
        viewModel.initTimer(totalTime)
    }
    // UI逻辑
}

该方案的问题在于ViewModel状态依赖外部触发初始化,违反了MVVM中ViewModel自我管理状态的原则,配置变更时可能出现状态错乱,因此仅推荐在简单场景下临时使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 02:57:34