如果我有多个从存储库获取的参数,这些参数是从数据存储中获取的。我应该如何正确初始化它们?
这是我目前的做法:
@HiltViewModel
class UserDataViewModel @Inject constructor(private val userDataRepository: UserDataRepository) : ViewModel() {
private val _userIdFlow = MutableStateFlow<String>(AppDefaults.DEFAULT_USER_ID)
val userIdFlow: StateFlow<String> = _userIdFlow.asStateFlow()
private val _userNameFlow = MutableStateFlow<String>(AppDefaults.DEFAULT_USER_NAME)
val userNameFlow: StateFlow<String> = _userNameFlow.asStateFlow()
private val _userEmailFlow = MutableStateFlow<String>(AppDefaults.DEFAULT_USER_EMAIL)
val userEmailFlow: StateFlow<String> = _userEmailFlow.asStateFlow()
private val _isPremiumUserFlow = MutableStateFlow<Boolean>(AppDefaults.DEFAULT_IS_PREMIUM_USER)
val isPremiumUserFlow: StateFlow<Boolean> = _isPremiumUserFlow.asStateFlow()
private val _isFirstTimeUserFlow = MutableStateFlow<Boolean>(AppDefaults.DEFAULT_IS_FIRST_TIME_USER)
val isFirstTimeUserFlow: StateFlow<Boolean> = _isFirstTimeUserFlow.asStateFlow()
private val _isLoggedInFlow = MutableStateFlow<Boolean>(AppDefaults.DEFAULT_IS_LOGGED_IN)
val isLoggedInFlow: StateFlow<Boolean> = _isLoggedInFlow.asStateFlow()
val isDataLoaded = MutableStateFlow(false)
init {
loadAllData()
}
private fun loadAllData() {
viewModelScope.launch {
// Launch all load operations in parallel using async
val userIdJob = async { loadUserId() }
val userNameJob = async { loadUserName() }
val userEmailJob = async { loadUserEmail() }
val isPremiumUserJob = async { loadIsPremiumUser() }
val isFirstTimeUserJob = async { loadIsFirstTimeUser() }
val isLoggedInJob = async { loadIsLoggedIn() }
try {
Log.d("Init", "Start loading data")
// Await all jobs to complete
awaitAll(
userIdJob,
userNameJob,
userEmailJob,
isPremiumUserJob,
isFirstTimeUserJob,
isLoggedInJob,
)
Log.d("Init", "Data Loaded")
} catch (e: Exception) {
// Handle any errors
Log.e("UserDataViewModel", "Error loading data", e)
} finally {
withContext(Dispatchers.Main) {
isDataLoaded.value = true
}
}
}
}
private suspend fun loadUserId() {
userDataRepository.userIdFlow
.catch { Log.e("UserDataViewModel", "Error loading userId", it) }
.collect { _userIdFlow.value = it ?: AppDefaults.DEFAULT_USER_ID }
}
private suspend fun loadUserName() {
userDataRepository.userNameFlow
.catch { Log.e("UserDataViewModel", "Error loading userName", it) }
.collect { _userNameFlow.value = it ?: AppDefaults.DEFAULT_USER_NAME }
}
private suspend fun loadUserEmail() {
userDataRepository.userEmailFlow
.catch { Log.e("UserDataViewModel", "Error loading userEmail", it) }
.collect { _userEmailFlow.value = it ?: AppDefaults.DEFAULT_USER_EMAIL }
}
private suspend fun loadIsPremiumUser() {
userDataRepository.isPremiumUserFlow
.catch { Log.e("UserDataViewModel", "Error loading isPremiumUser", it) }
.collect { _isPremiumUserFlow.value = it ?: AppDefaults.DEFAULT_IS_PREMIUM_USER }
}
private suspend fun loadIsFirstTimeUser() {
userDataRepository.isFirstTimeUserFlow
.catch { Log.e("UserDataViewModel", "Error loading isFirstTimeUser", it) }
.collect {
_isFirstTimeUserFlow.value = it ?: AppDefaults.DEFAULT_IS_FIRST_TIME_USER
}
}
private suspend fun loadIsLoggedIn() {
userDataRepository.isLoggedInFlow
.catch { Log.e("UserDataViewModel", "Error loading isLoggedIn", it) }
.collect {
_isLoggedInFlow.value = it ?: AppDefaults.DEFAULT_IS_LOGGED_IN
}
}
fun setUserId(userId: String) {
viewModelScope.launch {
userDataRepository.setUserId(userId)
}
}
fun setUserName(userName: String) {
viewModelScope.launch {
userDataRepository.setUserName(userName)
}
}
fun setUserEmail(userEmail: String) {
viewModelScope.launch {
userDataRepository.setUserEmail(userEmail)
}
}
fun setIsPremiumUser(isPremiumUser: Boolean) {
viewModelScope.launch {
userDataRepository.setIsPremiumUser(isPremiumUser)
}
}
fun setIsFirstTimeUser(isFirstTimeUser: Boolean) {
viewModelScope.launch {
userDataRepository.setIsFirstTimeUser(isFirstTimeUser)
Log.d("ViewModel", "setIsFirstTimeUser: $isFirstTimeUser")
}
}
fun setIsLoggedIn(isLoggedIn: Boolean) {
viewModelScope.launch {
userDataRepository.setIsLoggedIn(isLoggedIn)
}
}
}
初始化从
init
块开始。
我希望仅在块完成并加载所有数据后才设置 isDataLoaded 。
您不应该在视图模型中收集流。这个想法是只是转换来自较低层的流以将它们暴露给UI。
如果您不首先将数据存储值拆分为多个流,则可以极大地简化视图模型。只需将值捆绑到专用数据类中,如下所示:
data class User(
val id: String,
val name: String,
val email: String,
val isPremium: Boolean,
val isFirstTime: Boolean,
val isLoggedIn: Boolean,
)
假设您的数据存储有这些密钥:
private val Keys = object {
val USER_ID = stringPreferencesKey("user_id")
val USER_NAME = stringPreferencesKey("user_name")
val USER_EMAIL = stringPreferencesKey("user_email")
val IS_PREMIUM_USER = booleanPreferencesKey("is_premium_user")
val IS_FIRST_TIME_USER = booleanPreferencesKey("is_first_time_user")
val IS_LOGGED_IN = booleanPreferencesKey("is_logged_in")
}
然后您只需将数据存储流转换为
Flow<User>
,如下所示:
val user: Flow<User> = dataStore.data
.map { prefs ->
User(
id = prefs[Keys.USER_ID] ?: AppDefaults.DEFAULT_USER_ID,
name = prefs[Keys.USER_NAME] ?: AppDefaults.DEFAULT_USER_NAME,
email = prefs[Keys.USER_EMAIL] ?: AppDefaults.DEFAULT_USER_EMAIL,
isPremium = prefs[Keys.IS_PREMIUM_USER] ?: AppDefaults.DEFAULT_IS_PREMIUM_USER,
isFirstTime = prefs[Keys.IS_FIRST_TIME_USER] ?: AppDefaults.DEFAULT_IS_FIRST_TIME_USER,
isLoggedIn = prefs[Keys.IS_LOGGED_IN] ?: AppDefaults.DEFAULT_IS_LOGGED_IN,
)
}
当您的存储库将此流程传递到视图模型时,视图模型可以将其转换为 UI 状态:
val userUiState: StateFlow<UserUiState> = userDataRepository.userFlow
.mapLatest { UserUiState.Success(it) as UserUiState }
.catch { emit(UserUiState.Error(it.message)) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = UserUiState.Loading,
)
就是这样,您不再需要
init
或 loadAllData
,也不需要以前单独处理每个用户属性的任何函数。
您只需要一个新的
UserUiState
类型:
sealed interface UserUiState {
data object Loading : UserUiState
data class Success(val user: User) : UserUiState
data class Error(val message: String?) : UserUiState
}
这就是视图模型现在向 UI 公开的内容。在您的可组合项中,您可以像这样使用 ui 状态:
val uiState = viewModel.userUiState.collectAsStateWithLifecycle().value
when (uiState) {
is UserUiState.Loading -> CircularProgressIndicator()
is UserUiState.Error -> ErrorDialog(uiState.message)
is UserUiState.Success -> UserDetails(uiState.user)
}
这样您就不再需要主动收集流,只需将存储库流转换为
StateFlow<UserUiState>
,最终收集到您的可组合项中。
您还可以考虑将单独的更新函数合并为一个
fun setUser(user: User)
。然后数据存储可以相应地设置首选项。通过这种方式,您可以删除视图模型、存储库和数据存储中的大量样板代码。如果您想更新单个属性,您现在需要传递整个用户对象。这是否适合您可能取决于您的具体用例。