使用协程和流程时从 Repository 和 ViewModel 收集数据

Collecting Data from Repository and ViewModel when using Coroutines and Flow

提问人:Tarek Hendi 提问时间:11/6/2023 更新时间:11/6/2023 访问量:44

问:

大家好,我目前正在开发一个缓存应用程序,其中遇到以下情况: 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。

android kotlin 缓存 android-room 协程

评论


答:

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 在重新启动后继续运行。如果在超时之前观察到另一个屏幕,则不会取消流。