Kotlin ViewModel的 StateFlow:正确更新状态变更

移动开发 2026-07-08

这个问题是为了理解在ViewModel(视图模型)中的StateFlow。如何正确地对同一值进行多次更新。因此,出现一个颇为有趣的问题:第一次更新的值发生在调用 authV1 时,但在调用 authV2reset 时却没有更新。如何确保状态被正确复制,以确保state-flow得到更新,并让视图显示最新的值。请注意,视图本身只是显示一个 Column,其中包含一个 Text 来显示状态,还有一些 Button 视图用于更新state-flow。

class AuthViewModel: ViewModel() { // transient value/state

    private val _authEntityMutableStateFlow = MutableStateFlow<AuthEntity>(AuthEntity(token = ""))
    val authEntityStateFlow = _authEntityMutableStateFlow.asStateFlow()

    fun update(data: AuthEntity) {
        // required in kotlin because it does not update by value
        _authEntityMutableStateFlow.value = _authEntityMutableStateFlow.value.copy(token = data.token) // or authEntityStateFlow?
    }

    fun authV1() {
        viewModelScope.launch {
            update(AuthRepo.instance.getTestTokenV1()) // this method uses suspend call withContext
        }
    }

    fun authV2() {
        viewModelScope.launch {
            AuthRepo.instance.getTestTokenV2() // this method uses return withContext(Dispatchers.IO) { Result.success for testing with fixed value
                .onSuccess { authRespEntity ->
                    update(authRespEntity.data)
                }
        }
    }

    fun reset() {
        update(AuthEntity(token = ""))
    }
}

@Composable
fun AuthScreen(authViewModel: AuthViewModel = AuthViewModel()) {
    val authEntityStateFlow = authViewModel.authEntityStateFlow.collectAsStateWithLifecycle()
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("authEntityStateFlow: ${authEntityStateFlow.value}")
        Button(onClick =  {
            authViewModel.authV1()
        }) {
            Text("authV1")
        }
        Button(onClick =  {
            authViewModel.authV2()
        }) {
            Text("authV2")
        }
        Button(onClick =  {
            authViewModel.reset()
        }) {
            Text("reset")
        }
    }
}

以下是代码中使用的模型类:

import kotlinx.serialization.Serializable

@Serializable
data class AuthRespEntity(val data: AuthEntity)

@Serializable
data class AuthEntity(val token: String)

解决方案

这与Flow无关,这个问题源自对ViewModel的实例化不当。

你在你的可组合函数(Composable)中这样使用:

authViewModel: AuthViewModel = AuthViewModel()

这会在可组合函数每次重新组合时创建一个新的ViewModel实例。重新组合不能被直接控制,但至少在 authEntityStateFlow 的值每次改变时就会发生。因此,每当该值改变时,都会创建一个新的ViewModel实例,带有一个新的Flow,而这个新的Flow初始时包含空字符串 ""。这就是为什么每次按钮点击都会导致文本被清空。

要修复这个问题,你需要让ViewModel实例在重新组合时保持存活。你希望每次都使用同一个ViewModel实例。为实现这一点,你需要使用一个工厂函数,在某处保存ViewModel实例,以便在每次后续调用时都能返回同一个实例。幸运的是,库 androidx.lifecycle:lifecycle-viewmodel-compose 已经提供了一个方便的辅助函数来实现这一点:

authViewModel: AuthViewModel = viewModel()

如果你在使用依赖注入框架Hilt或 Koin,你必须改用 hiltViewModel()koinViewModel()


顺便提一句,如果你需要MutableStateFlow的“旧”值来计算“新”值,不应该使用 value 属性,因为它并非线程安全。请改用 update 函数:

_authEntityMutableStateFlow.update { it.copy(token = data.token) }
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章