Android架构:Repository模式+RxJava,选Flowable还是Single?
设计新闻流App的Repository接口:适配MVVM与View数据订阅
先明确核心:单一数据源(SSOT)的意义
你提到的SSOT在Repository层的核心价值,就是让View层只从一个统一入口获取数据——不管数据是来自网络API、本地缓存还是其他来源,Repository都要把这些细节封装起来,对外暴露一致的数据流。这样View不用关心数据从哪来,只需要专注于展示即可。
具体的接口设计思路
我结合你用的新闻API和MVVM架构,给你一套可落地的方案:
1. 定义Repository接口:暴露可观察的数据流
因为View需要订阅数据更新,接口里的方法应该返回可观察类型(比如Kotlin的Flow,或者Android的LiveData)。这里以Kotlin Flow为例,更适合现代Android开发:
// 先定义一个密封类处理数据状态,方便View区分加载、成功、失败 sealed class Result<out T> { object Loading : Result<Nothing>() data class Success<out T>(val data: T) : Result<T>() data class Error(val message: String) : Result<Nothing>() } interface NewsRepository { // 获取指定国家的头条新闻,返回可订阅的Flow fun getTopHeadlines(country: String): Flow<Result<List<Article>>> // 搜索新闻的方法,同样返回Flow fun searchNews(query: String): Flow<Result<List<Article>>> }
用Result密封类封装状态,能让View层清晰处理不同场景(比如加载时显示进度条,失败时显示错误提示)。
2. 实现Repository:封装多数据源逻辑(SSOT的关键)
Repository的实现类要负责整合网络请求和本地缓存,对外只暴露统一的数据流。比如:
class NewsRepositoryImpl( private val newsApi: NewsApi, // 你封装的新闻API请求类 private val newsDao: NewsDao // Room数据库的Dao,用于本地缓存 ) : NewsRepository { override fun getTopHeadlines(country: String): Flow<Result<List<Article>>> = flow { // 第一步:先发射本地缓存数据,让View快速显示内容 emit(Result.Success(newsDao.getTopHeadlines(country))) try { // 第二步:发起网络请求获取最新数据 val apiResponse = newsApi.getTopHeadlines(country) if (apiResponse.isSuccessful) { apiResponse.body()?.articles?.let { remoteArticles -> // 更新本地缓存 newsDao.insertTopHeadlines(remoteArticles, country) // 发射最新的网络数据,View会自动更新 emit(Result.Success(remoteArticles)) } ?: emit(Result.Error("接口返回空数据")) } else { emit(Result.Error("请求失败:${apiResponse.message()}")) } } catch (e: Exception) { // 网络异常时,发射错误状态,但保留之前的缓存数据 emit(Result.Error("网络错误:${e.localizedMessage ?: "未知错误"}")) } }.flowOn(Dispatchers.IO) // 指定数据流在IO线程执行,避免阻塞主线程 }
这里完全体现了SSOT:View只需要订阅getTopHeadlines的Flow,Repository自己处理“先缓存后网络”的逻辑,View无需感知数据源的切换。
3. 在MVVM中连接ViewModel与Repository
ViewModel作为View和Repository的中间层,持有Repository实例,并将数据流转换为View可观察的状态:
class NewsViewModel( private val newsRepository: NewsRepository ) : ViewModel() { // 内部可变状态,外部只暴露不可变的StateFlow private val _topHeadlines = MutableStateFlow<Result<List<Article>>>(Result.Loading) val topHeadlines: StateFlow<Result<List<Article>>> = _topHeadlines // 触发获取头条新闻的方法 fun fetchTopHeadlines(country: String = "us") { viewModelScope.launch { newsRepository.getTopHeadlines(country).collect { result -> _topHeadlines.value = result } } } }
View层(比如Compose组件或者Fragment)只需要订阅topHeadlines这个StateFlow,就能自动收到数据更新,无需手动管理订阅生命周期。
几个关键注意点
- 单一职责:Repository只负责数据的获取、缓存和分发,不要加入任何UI相关的逻辑(比如Toast提示、Activity跳转)。
- 状态封装:一定要用类似
Result的类封装数据状态,让View层能明确处理加载、成功、失败三种场景。 - 线程安全:Repository里的数据流处理要放在IO线程,ViewModel用
viewModelScope管理协程,确保主线程安全。
内容的提问来源于stack exchange,提问作者Elias
相关产品推荐
相关产品推荐

