Clean Architecture(MVVM)下跨ViewModel传递UserData至UI State最优方案
我有一个MainViewModel,它会根据AuthState和当前用户uid,通过UseCase与Repository从Firestore获取UserData,并以StateFlow形式提供给所有屏幕更新UI。另外有一个编辑资料的ProfileScreen,对应EditProfileViewModel与EditProfileUIState。
现在需要将MainViewModel中的UserData传入EditProfileUIState,供用户修改后由EditProfileViewModel更新至Firestore。目前我在Composable中通过LaunchEffect传递数据,但不确定该方案是否合理。想知道在Clean Architecture(MVVM)架构下,实现该需求的最优方式是什么?是否可通过UseCase实现?我不想在每个需要UserData的ViewModel中重复处理响应逻辑。
MainViewModel.kt
private val _userData = MutableStateFlow<UserData?>(null) val userData: StateFlow<UserData?> = _userData init { getAuthState() } private fun getAuthState(){ viewModelScope.launch { authUseCases.getAuthState(viewModelScope).collectLatest { when(it){ true -> { _userData.update { null } } false -> { authUseCases.currentUser()?.uid?.let { userId -> getUserData(userId) } ?: Log.d("PROVES","CAN'T GET USER") } } } } } private fun getUserData(userId: String){ Log.d("PROVES","GetUserData") viewModelScope.launch { userUseCases.getUserData(userId) .collectLatest { response -> when(response) { is Success -> { _userData.update { response.data } Log.d("PROVES","${response.data}") } is Failure -> { Log.d("PROVES","GET DATA FAILURE RESPONSE") } } } } }
ProfileEditUIState.kt
data class ProfileEditUIState (val isLoading: Boolean = false, val userData: UserData? = null)
ProfileEditViewModel.kt
private val _profileEditUiState = MutableStateFlow(ProfileEditUIState()) val profileEditUiState: StateFlow<ProfileEditUIState> = _profileEditUiState.asStateFlow() fun onEvent(event: ProfileEditUIEvent) { when(event) { is ProfileEditUIEvent.UserDataLoaded -> { _profileEditUiState.update { it.copy(userData = event.userData) } } } }
屏幕Composable代码
fun ProfileEditContent( modifier: Modifier = Modifier, mainViewModel: MainViewModel = hiltViewModel(), profileEditViewModel: ProfileEditViewModel = hiltViewModel(), snackbarHostState: SnackbarHostState, onSignInClick: () -> Unit, ) { val state by profileEditViewModel.profileEditUiState.collectAsStateWithLifecycle() val userData by mainViewModel.userData.collectAsStateWithLifecycle() userData?.let { LaunchedEffect(true) { profileEditViewModel.onEvent(ProfileEditUIEvent.UserDataLoaded(it)) } } ...
当前方案的问题
你现在用LaunchedEffect(true)传递数据的方式存在两个明显问题:
- 关注点分离缺失:Composable层承担了ViewModel间通信的职责,不符合MVVM分层原则,增加了UI层的复杂度。
- 数据更新不及时:
LaunchedEffect(true)只会执行一次,如果MainViewModel中的UserData后续发生变化,EditProfileViewModel无法感知并更新。
推荐方案:让EditProfileViewModel直接依赖UseCase获取UserData(符合Clean Architecture)
这种方案完全遵循分层原则,每个ViewModel只依赖对应UseCase,避免ViewModel间耦合,同时复用已有的getUserData UseCase,不用重复编写响应逻辑。
步骤1:修改EditProfileViewModel,直接获取UserData
class EditProfileViewModel @Inject constructor( private val authUseCases: AuthUseCases, private val userUseCases: UserUseCases ) : ViewModel() { private val _profileEditUiState = MutableStateFlow(ProfileEditUIState()) val profileEditUiState: StateFlow<ProfileEditUIState> = _profileEditUiState.asStateFlow() init { loadUserData() } private fun loadUserData() { viewModelScope.launch { // 获取当前用户uid authUseCases.currentUser()?.uid?.let { userId -> // 复用已有的getUserData UseCase userUseCases.getUserData(userId).collectLatest { response -> handleUserDataResponse(response) } } } } // 统一处理响应逻辑,可复用至其他ViewModel private fun handleUserDataResponse(response: Result<UserData>) { when(response) { is Success -> { _profileEditUiState.update { it.copy(userData = response.data) } } is Failure -> { // 这里可以统一处理失败逻辑,比如记录日志、发送错误事件等 Log.d("PROVES","GET DATA FAILURE RESPONSE") } } } // 处理编辑提交逻辑 fun onProfileSubmit(updatedUserData: UserData) { viewModelScope.launch { userUseCases.updateUserData(updatedUserData).collectLatest { response -> // 同样复用统一的响应处理逻辑 when(response) { is Success -> { /* 处理成功,比如弹出提示 */ } is Failure -> { /* 处理失败 */ } } } } } }
步骤2:优化响应逻辑复用(可选)
如果多个ViewModel都需要处理类似的Result响应,可以把响应处理封装成扩展函数,彻底消除重复代码:
// 扩展函数:统一处理Result类型的Flow响应 fun <T> Flow<Result<T>>.handleResult( viewModelScope: CoroutineScope, onSuccess: (T) -> Unit, onFailure: (() -> Unit)? = null ) = viewModelScope.launch { collectLatest { response -> when(response) { is Success -> onSuccess(response.data) is Failure -> onFailure?.invoke() ?: Log.d("PROVES","OPERATION FAILURE") } } }
然后在ViewModel中简化调用:
private fun loadUserData() { authUseCases.currentUser()?.uid?.let { userId -> userUseCases.getUserData(userId).handleResult( viewModelScope = viewModelScope, onSuccess = { data -> _profileEditUiState.update { it.copy(userData = data) } }, onFailure = { /* 自定义失败处理 */ } ) } }
步骤3:简化Composable代码
现在Composable无需再处理ViewModel间的通信,直接依赖EditProfileViewModel即可:
fun ProfileEditContent( modifier: Modifier = Modifier, profileEditViewModel: ProfileEditViewModel = hiltViewModel(), snackbarHostState: SnackbarHostState, onSignInClick: () -> Unit, ) { val state by profileEditViewModel.profileEditUiState.collectAsStateWithLifecycle() // 直接使用state.userData渲染UI ... }
备选方案:使用共享的User状态管理类
如果你的App中有大量屏幕需要实时获取UserData,可以创建一个单例的UserManager类(通过Hilt注入为Singleton),由MainViewModel负责更新它的状态,其他ViewModel直接依赖UserManager获取最新的UserData:
@Singleton class UserManager @Inject constructor() { private val _userData = MutableStateFlow<UserData?>(null) val userData: StateFlow<UserData?> = _userData fun updateUserData(data: UserData?) { _userData.value = data } }
然后修改MainViewModel,更新UserManager的状态:
class MainViewModel @Inject constructor( private val authUseCases: AuthUseCases, private val userUseCases: UserUseCases, private val userManager: UserManager ) : ViewModel() { // 移除原有的_userData,直接复用userManager的state val userData = userManager.userData private fun getUserData(userId: String){ viewModelScope.launch { userUseCases.getUserData(userId) .collectLatest { response -> when(response) { is Success -> { userManager.updateUserData(response.data) } is Failure -> { Log.d("PROVES","GET DATA FAILURE RESPONSE") } } } } } }
EditProfileViewModel直接依赖UserManager:
class EditProfileViewModel @Inject constructor( private val userManager: UserManager, private val userUseCases: UserUseCases ) : ViewModel() { private val _profileEditUiState = MutableStateFlow(ProfileEditUIState()) val profileEditUiState: StateFlow<ProfileEditUIState> = _profileEditUiState.asStateFlow() init { viewModelScope.launch { userManager.userData.collectLatest { userData -> _profileEditUiState.update { it.copy(userData = userData) } } } } }
这种方案适合需要全局共享用户状态的场景,但要注意避免过度使用单例导致状态管理复杂化。
- 最优方案:让
EditProfileViewModel直接依赖getUserDataUseCase获取数据,符合Clean Architecture的分层原则,职责清晰,复用性强,避免了ViewModel间的耦合。 - 避免重复逻辑:通过封装统一的响应处理扩展函数,消除多个ViewModel中的重复代码。
- 不推荐原方案:原有的
LaunchEffect传递数据方式违反了MVVM分层原则,且无法处理数据更新的情况。
内容的提问来源于stack exchange,提问作者Alex Diaz Llagostera

