Android,MVI模式+;科特林流量+;房间+;改装,如何将它们装配在一起?
我是MVI模式的新手。所以我试图理解如何将所有的部分组合在一起。 我有一个应用程序,我使用MVI模式构建(或者至少这是我的本意)。我有我的片段(我使用了导航组件,但目前只关注一个片段),它由自己的ViewModel支持。然后我有一个repository类,其中所有viewmodels都检索数据。存储库有两个数据源,一个web API和一个用作数据缓存的本地DB,我使用了用于DB管理的空间。 我尝试了不同的方法来解决这个问题。目前,我是这样做的: 在DAO中,我使用此指令从数据库检索数据:Android,MVI模式+;科特林流量+;房间+;改装,如何将它们装配在一起?,android,android-fragments,mvvm,android-room,flow,Android,Android Fragments,Mvvm,Android Room,Flow,我是MVI模式的新手。所以我试图理解如何将所有的部分组合在一起。 我有一个应用程序,我使用MVI模式构建(或者至少这是我的本意)。我有我的片段(我使用了导航组件,但目前只关注一个片段),它由自己的ViewModel支持。然后我有一个repository类,其中所有viewmodels都检索数据。存储库有两个数据源,一个web API和一个用作数据缓存的本地DB,我使用了用于DB管理的空间。 我尝试了不同的方法来解决这个问题。目前,我是这样做的: 在DAO中,我使用此指令从数据库检索数据: @Qu
@Query("SELECT * FROM Users WHERE idTool=:idTool AND nickname LIKE '%' || :query || '%'")
fun users(idTool: Int, query: String) : Flow<List<User>>
在ViewModel中,我创建了两个由中介LiveData协调的可变LiveData:
val nicknameQuery = MutableStateFlow("")
private val nicknameQueryFlow = nicknameQuery.flatMapLatest {
repository.usersFlow(idToolQuery.value, it)
}
val idToolQuery = MutableStateFlow(DEFAULT_TOOL_ID)
private val idToolQueryFlow = idToolQuery.flatMapLatest {
repository.usersFlow(it, nicknameQuery.value)
}
val users = MediatorLiveData<List<User>>()
init {
users.addSource(nicknameQueryFlow.asLiveData()) {
users.value = it
}
users.addSource(idToolQueryFlow.asLiveData()) {
users.value = it
}
fetchUsers()
}
如果已经有使用此特定idTool的缓存用户,则此方法将检查数据库,如果没有,则从web获取用户,并将检索到的数据存储到数据库中。这是我的存储库类中的方法:
suspend fun fetchUsers(
idTool: Int,
forceRefetch: Boolean = false
): Flow<DataState<List<User>>> = flow {
try {
var cachedUser = userDao.users(idTool, "").first()
val users: List<User>
if(cachedUser.isEmpty() || forceRefetch) {
Log.d(TAG, "Retrieve users: from web")
emit(DataState.Loading)
withContext(Dispatchers.IO) {
appJustOpen = false
val networkUsers =
api.getUsers(
idTool,
"Bearer ${sessionClient.tokens.accessToken.toString()}"
)
users = entityMapper.mapFromEntitiesList(networkUsers)
userDao.insertList(users)
}
} else {
users = cachedUser
}
emit(DataState.Success(users))
} catch (ex: Exception) {
emit(DataState.Error(ex))
}
}
最后,在我的片段中,我有:
userListViewModel.dataState.observe(viewLifecycleOwner, { dataState ->
when (dataState) {
is DataState.Success -> {
showUserList()
}
is DataState.Error -> {
Log.e("TEST", dataState.exception.toString())
hideLoader()
Toast.makeText(activity, "Error retrieving data: ${dataState.exception}", Toast.LENGTH_LONG).show()
}
is DataState.Loading -> {
showLoader()
}
else -> {
// Do Nothing in any other case
}
}
})
此时,Success state获取了一个用户列表,但该列表来自于以前的方法,此时它是无用的,因为在获取数据后,列表被插入到数据库中,我有一个到数据库的流,它负责更新UI。这样,当我更改idTool、更改查询、删除用户时,视图总是会得到通知
这种方法正确吗
在此之前,我使用了另一种方法。我返回的不是来自数据库的流,而是一个列表。然后我的fetchUsers总是返回一个DataState,它在DB中进行检查,如果没有找到任何东西,它就会从web上获取数据并返回该列表。这种方法给我带来了一些问题,因为每次我更改idTool或query时,都必须调用fetchUsers方法。即使从数据库中删除了用户,视图也不会得到通知,因为我并没有和数据库直接通信
suspend fun fetchUsers(
idTool: Int,
forceRefetch: Boolean = false
): Flow<DataState<List<User>>> = flow {
try {
var cachedUser = userDao.users(idTool, "").first()
val users: List<User>
if(cachedUser.isEmpty() || forceRefetch) {
Log.d(TAG, "Retrieve users: from web")
emit(DataState.Loading)
withContext(Dispatchers.IO) {
appJustOpen = false
val networkUsers =
api.getUsers(
idTool,
"Bearer ${sessionClient.tokens.accessToken.toString()}"
)
users = entityMapper.mapFromEntitiesList(networkUsers)
userDao.insertList(users)
}
} else {
users = cachedUser
}
emit(DataState.Success(users))
} catch (ex: Exception) {
emit(DataState.Error(ex))
}
}
private val _dataState = MutableLiveData<DataState<List<User>>>()
val dataState: LiveData<DataState<List<User>>> get() = _dataState
private fun fetchUsers() {
viewModelScope.launch {
repository.fetchUsers(DEFAULT_TOOL_ID).collect {
_dataState.value = it
}
}
}
userListViewModel.dataState.observe(viewLifecycleOwner, { dataState ->
when (dataState) {
is DataState.Success -> {
showUserList()
}
is DataState.Error -> {
Log.e("TEST", dataState.exception.toString())
hideLoader()
Toast.makeText(activity, "Error retrieving data: ${dataState.exception}", Toast.LENGTH_LONG).show()
}
is DataState.Loading -> {
showLoader()
}
else -> {
// Do Nothing in any other case
}
}
})