提问人:Tarek Hendi 提问时间:11/6/2023 更新时间:11/6/2023 访问量:44
使用协程和流程时从 Repository 和 ViewModel 收集数据
Collecting Data from Repository and ViewModel when using Coroutines and Flow
问:
大家好,我目前正在开发一个缓存应用程序,其中遇到以下情况: DAO 函数返回一个 Flow<List>表示来自本地数据库的数据流。在存储库中,我从 DAO 函数收集数据,在 ViewModel 中,我从存储库函数收集数据。
我想讨论一下从存储库和 ViewModel 收集数据的最佳实践。在这两个地方收集数据是否被认为是一种好的做法?这种方法是否有任何潜在的缺点或问题?
为了提供更多信息,本地数据库充当应用的真实来源,主要数据处理在那里进行。由于 DAO 函数返回 Flow<List>而存储库函数返回 Flow<List>,因此似乎有必要从存储库中的 DAO 函数收集数据以观察数据库的更新。
我非常感谢您关于从存储库和 ViewModel 收集数据的最佳实践的建议和经验,同时考虑协程和 Flow 的使用。
道:
@Dao
interface MovieDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveMovies(moviesEntity: MoviesEntity)
@Query("SELECT * FROM movies")
fun getMovies(): Flow<List<MoviesEntity>>?
@Query("DELETE FROM movies")
suspend fun clearMoviesListing()
}
接口 MovieRepository:
interface MovieRepository {
fun getMoviesResult(
category: String,
fetchFromRemote: Boolean,
): Flow<Resource<List<Movies>>>
}
MovieRepositoryImpl:
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
class MovieRepositoryImpl(
private val movieApi: MovieApi,
private val movieDao: MovieDao
) : MovieRepository {
override fun getMoviesResult(
category: String,
fetchFromRemote: Boolean
): Flow<Resource<List<Movies>>> {
return flow {
while (true) {
emit(Resource.Loading(true))
movieDao.getMovies()?.collect { localListing ->
if (localListing.isNotEmpty() && !fetchFromRemote) {
emit(Resource.Loading(false))
emit(
Resource.Success(data = localListing.map {
it.toMovies()
})
)
} else {
val remoteListing = try {
movieApi.getMovies(category = category)
} catch (e: IOException) {
e.printStackTrace()
emit(Resource.Error("Couldn't Reach Server, Check Your Internet Connection, Try Refresh"))
null
} catch (e: HttpException) {
e.printStackTrace()
emit(Resource.Error("Couldn't Reach Server, Check Your Internet Connection."))
null
}
if (remoteListing !== null) {
emit(Resource.Loading(false))
remoteListing.let { movie ->
movieDao.clearMoviesListing()
movieDao.saveMovies(movie.toMoviesEntity())
movieDao.getMovies()!!.collect() { movieEntity ->
emit(
Resource.Success(data = movieEntity.map {
it.toMovies()
})
)
}
}
}
emit(Resource.Error(message = ""))
emit(Resource.Loading(false))
}
}
}
}
}
}
MovieScreenViewModel:
private fun getMovieResult(category: String, fetchFromRemote: Boolean) {
viewModelScope.launch {
movieRepository.getMoviesResult(category, fetchFromRemote)
.collect { results ->
when (results) {
is Resource.Success -> {
results.data?.let { result ->
state = state.copy(movies = result)
}
}
is Resource.Error ->
state = state.copy(
error = results.message ?: "An unexpected error occurred"
)
is Resource.Loading -> state = state.copy(isLoading = results.isLoading)
}
}
}
}
感谢您的宝贵意见!
我不确定在ViewModel中再次收集数据是否合适,或者是否有更有效的方法。我想确保我遵循最佳实践,并在我的应用程序中充分利用协程和 Flow。
答:
0赞
Purple6666
11/6/2023
#1
我对 android 开发也很陌生,但根据 Android 的一般准则,您应该在视图模型中使用如下内容:
lateinit var movieListLiveData: MutableLiveData<List<Movies>>
private fun getMovieResult(category: String, fetchFromRemote: Boolean) {
movieListLiveData = movieRepository.getMoviesResult(category, fetchFromRemote).asLiveData()
}
并使用该实时数据来更新您的 UI
asLiveData 运算符将 Flow 转换为具有可配置超时的 LiveData。 就像 liveData 构建器一样,超时将帮助 Flow 在重新启动后继续运行。如果在超时之前观察到另一个屏幕,则不会取消流。
评论