Android学习之ViewModel与Repository的关
该系列文章为自学过程中的产出,若有错误,希望热心路人不吝赐教
之前的文章Android学习之MVVM简谈过MVVM,这章着重讲一下如何在Android的代码中表现ViewModel
Responsibility(职责)
既然View只管视图相关的逻辑,Model只管数据与规范,那ViewModel就注定要做最脏最累的活了(名字长的代价)。
网络调用谁做?ViewModel做!
IO谁做?ViewModel做!
保存分页参数谁做?ViewModel做!
1+1=?谁做?还是ViewModel做!
ViewModel code
贴一段Google的sample code:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.google.samples.apps.sunflower.data.UnsplashPhoto
import com.google.samples.apps.sunflower.data.UnsplashRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@HiltViewModel // hilt依赖,修改这个class,告诉编译器,这个class是一个ViewModel
class GalleryViewModel @Inject constructor( // inject,告诉编译器,这个class构造器里面的内容被我注入了,麻烦帮我初始化它
private val repository: UnsplashRepository // ViewModel是苦力,需要去仓库里面取一些需要的东西
) : ViewModel() {
private var currentQueryValue: String? = null // 把搜索的参数保存起来
private var currentSearchResult: Flow<PagingData<UnsplashPhoto>>? = null // 把搜索的结果保存起来
fun searchPictures(queryString: String): Flow<PagingData<UnsplashPhoto>> {
currentQueryValue = queryString
val newResult: Flow<PagingData<UnsplashPhoto>> =
repository.getSearchResultStream(queryString).cachedIn(viewModelScope)
currentSearchResult = newResult
return newResult
}
}
在代码中,看到了构造器中注入了一个Repository,那这个Repository在我们的开发中又担任了什么角色呢?
Repository code
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.google.samples.apps.sunflower.api.UnsplashService
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class UnsplashRepository @Inject constructor(private val service: UnsplashService) {
fun getSearchResultStream(query: String): Flow<PagingData<UnsplashPhoto>> {
return Pager(
config = PagingConfig(enablePlaceholders = false, pageSize = NETWORK_PAGE_SIZE),
pagingSourceFactory = { UnsplashPagingSource(service, query) }
).flow
}
companion object {
private const val NETWORK_PAGE_SIZE = 25
}
}
这时候我们看到repository里面又注入了一个service,这么复杂,又不知道是用来做什么的,为什么不能直接在ViewModel里面直接实现searchPictures的逻辑,何必又搞个Repository多此一举呢?不急,再看另一个类型的Repository
import javax.inject.Inject
import javax.inject.Singleton
/**
* Repository module for handling data operations.
*
* Collecting from the Flows in [PlantDao] is main-safe. Room supports Coroutines and moves the
* query execution off of the main thread.
*/
@Singleton
class PlantRepository @Inject constructor(private val plantDao: PlantDao) {
fun getPlants() = plantDao.getPlants()
fun getPlant(plantId: String) = plantDao.getPlant(plantId)
fun getPlantsWithGrowZoneNumber(growZoneNumber: Int) =
plantDao.getPlantsWithGrowZoneNumber(growZoneNumber)
}
这个Repository结构也类似,看到注释里面给我们解释了一下:
Repository模块是用来处理数据操作的
所以这时候我们可以假设有这么一个场景:有个用户登录界面,用户登录成功后,需要查询到最近未读的消息,然后在主页面显示出来。
那么按照正常的MVVM的App Architecture来分析的话就是:
首先View(Activity / Fragment)填写用户名密码后通知ViewModel更改了Model,然后触发登录按钮,这时候应该发出第一个api请求了。我们说过,网络调用也同样是ViewModel先处理的,这时候ViewModel来到跟用户有关的Repository,这个Repository有登录、注册、重置密码等函数,ViewModel把Model给了Repository,并点名需要调用登录有关的函数。Repository表示他只负责处理数据,并不发送请求,所以又把请求和Model继续交给了他的小弟,Service / DAO,Service在发出请求之后,把结果反馈给了他的直属上级Repository,Repository转交给了ViewModel,ViewModel把结果填充到了合适的Model,然后通知View进行了场景的跳转(navigate)。
跟着流程图应该会更容易理解一些,差不多就是这样了。