viewModel.articleList.observe(this, Observer {
adapter.submitList(it)
一个基于Paging的Database列表已经完成,是不是非常简单呢?如果需要完整代码可以查看Github
自定义DataSource
上面是通过Room来获取数据,但我们需要知道的是,Room之所以简单是因为它会帮我们自己实现许多数据库相关的逻辑代码,让我们只需关注与自己业务相关的逻辑即可。而这其中与Paging相关的是对DataSource与DataSource.Factory的具体实现。
但是我们实际开发中数据绝大多数来自于网络,所以DataSource与DataSource.Factory的实现还是要我们自己来啃。
所幸的是,对于DataSource的实现,Paging已经帮我们提供了三个非常全面的实现,分别是:
PageKeyedDataSource: 通过当前页相关的key来获取数据,非常常见的是key作为请求的page的大小。
ItemKeyedDataSource: 通过具体item数据作为key,来获取下一页数据。例如聊天会话,请求下一页数据可能需要上一条数据的id。
PositionalDataSource: 通过在数据中的position作为key,来获取下一页数据。这个典型的就是上面所说的在Database中的运用。
PositionalDataSource相信已经有点印象了吧,Room中默认帮我实现的就是通过PositionalDataSource来获取数据库中的数据的。
接下来我们通过使用最广的PageKeyedDataSource来实现网络数据。
基于Databases的三步,我们这里将它的第一步拆分为两步,所以我们只需四步就能实现Paging对网络数据的处理。
基于PageKeyedDataSource实现网络请求
实现DataSource.Factory
使用LiveData来观察PagedList
使用PagedListAdapter来与数据进行绑定与更
PageKeyedDataSource
我们自定义的DataSource需要实现PageKeyedDataSource,实现了之后会有如下三个方法需要我们去实现
class NewsDataSource(private val newsApi: NewsApi,
private val domains: String,
private val retryExecutor: Executor) : PageKeyedDataSource<Int, ArticleModel>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, ArticleModel>) {
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, ArticleModel>) {
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, ArticleModel>) {
复制代码
其中loadBefore暂时用不到,因为我这个实例是获取新闻列表,所以只需要loadInitial与loadAfter即可。
至于这两个方法的具体实现,其实没什么多说的,根据你的业务要求来即可,这里要说的是,数据获取完毕之后要回调方法第二个参数callback的onResult方法。例如loadInitial:
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, ArticleModel>) {
initStatus.postValue(Loading(""))
CompositeDisposable().add(getEverything(domains, 1, ArticleListModel::class.java)
.subscribeWith(object : DisposableObserver<ArticleListModel>() {
override fun onComplete() {
override fun onError(e: Throwable) {
retry = {
loadInitial(params, callback)
initStatus.postValue(Error(e.localizedMessage))
override fun onNext(t: ArticleListModel) {
initStatus.postValue(Success(200))
callback.onResult(t.articles, 1, 2)
复制代码
在onNext方法中,我们将获取的数据填充到onResult方法中,同时传入了之前的页码previousPageKey(初始化为第一页)与之后的页面nextPageKey,nextPageKey自然是作用于loadAfter方法。这样我们就可以在loadAfter中的params参数中获取到:
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, ArticleModel>) {
loadStatus.postValue(Loading(""))
CompositeDisposable().add(getEverything(domains, params.key, ArticleListModel::class.java)
.subscribeWith(object : DisposableObserver<ArticleListModel>() {
override fun onComplete() {
override fun onError(e: Throwable) {
retry = {
loadAfter(params, callback)
loadStatus.postValue(Error(e.localizedMessage))
override fun onNext(t: ArticleListModel) {
loadStatus.postValue(Success(200))
callback.onResult(t.articles, params.key + 1)
复制代码
这样DataSource就基本上完成了,接下来要做的是,实现DataSource.Factory来生成我们自定义的DataSource
DataSource.Factory
之前我们就已经提及到,DataSource.Factory只有一个abstract方法,我们只需实现它的create方法来创建自定义的DataSource即可:
class NewsDataSourceFactory(private val newsApi: NewsApi,
private val domains: String,
private val executor: Executor) : DataSource.Factory<Int, ArticleModel>() {
val dataSourceLiveData = MutableLiveData<NewsDataSource>()
override fun create(): DataSource<Int, ArticleModel> {
val dataSource = NewsDataSource(newsApi, domains, executor)
dataSourceLiveData.postValue(dataSource)
return dataSource
复制代码
嗯,代码就是这么简单,这一步也就完成了,接下来要做的是将pagedList进行LiveData封装。
Repository & ViewModel
这里与Database不同的是,并没有直接在ViewModel中通过DataSource.Factory来获取pagedList,而是进一步使用Repository进行封装,统一通过sendRequest抽象方法来获取NewsListingModel的封装结果实例。
data class NewsListingModel(val pagedList: LiveData<PagedList<ArticleModel>>,
val loadStatus: LiveData<LoadStatus>,
val refreshStatus: LiveData<LoadStatus>,
val retry: () -> Unit,
val refresh: () -> Unit)
sealed class LoadStatus : BaseModel()
data class Success(val status: Int) : LoadStatus()
data class NoMore(val content: String) : LoadStatus()
data class Loading(val content: String) : LoadStatus()
data class Error(val message: String) : LoadStatus()
复制代码
所以Repository中的sendRequest返回的将是NewsListingModel,它里面包含了数据列表、加载状态、刷新状态、重试与刷新请求。
class NewsRepository(private val newsApi: NewsApi,
private val domains: String,
private val executor: Executor) : BaseRepository<NewsListingModel> {
override fun sendRequest(pageSize: Int): NewsListingModel {
val newsDataSourceFactory = NewsDataSourceFactory(newsApi, domains, executor)
val newsPagingList = newsDataSourceFactory.toLiveData(
pageSize = pageSize,
fetchExecutor = executor
val loadStatus = Transformations.switchMap(newsDataSourceFactory.dataSourceLiveData) {
it.loadStatus
val initStatus = Transformations.switchMap(newsDataSourceFactory.dataSourceLiveData) {
it.initStatus
return NewsListingModel(
pagedList = newsPagingList,
loadStatus = loadStatus,
refreshStatus = initStatus,
retry = {
newsDataSourceFactory.dataSourceLiveData.value?.retryAll()
refresh = {
newsDataSourceFactory.dataSourceLiveData.value?.invalidate()
复制代码
接下来ViewModel中就相对来就简单许多了,它需要关注的就是对NewsListingModel中的数据进行分离成单个LiveData对象即可,由于本身其成员就是LiveDate对象,所以分离也是非常简单。分离是为了以便在Activity进行observe观察。
class NewsVM(app: Application, private val newsRepository: BaseRepository<NewsListingModel>) : AndroidViewModel(app) {
private val newsListing = MutableLiveData<NewsListingModel>()
val adapter = NewsAdapter {
retry()
val newsLoadStatus = Transformations.switchMap(newsListing) {
it.loadStatus
val refreshLoadStatus = Transformations.switchMap(newsListing) {
it.refreshStatus
val articleList = Transformations.switchMap(newsListing) {
it.pagedList
fun getData() {
newsListing.value = newsRepository.sendRequest(20)
private fun retry() {
newsListing.value?.retry?.invoke()
fun refresh() {
newsListing.value?.refresh?.invoke()
复制代码
PagedListAdapter & Activity
Adapter部分与Database的基本类似,主要也是需要实现DiffUtil.ItemCallback,剩下的就是正常的Adapter实现,我这里就不再多说了,如果需要的话请阅读源码
最后的observe代码
private fun addObserve() {
newsVM.articleList.observe(this, Observer {
newsVM.adapter.submitList(it)
newsVM.newsLoadStatus.observe(this, Observer {
newsVM.adapter.updateLoadStatus(it)
newsVM.refreshLoadStatus.observe(this, Observer {
refresh_layout.isRefreshing = it is Loading
refresh_layout.setOnRefreshListener {
newsVM.refresh()
newsVM.getData()
Paging封装的还是非常好的,尤其是项目中对RecyclerView非常依赖的,还是效果不错的。当然它的优点也是它的局限性,这一点也是没办法的事情。
希望你通过这篇文章能够熟悉运用Paging,如果这篇文章对你有所帮助,你可以顺手关注一波,这是对我最大的鼓励!
Android精华录
该库的目的是结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点
Android精华录
madroid
5年前
- 6.9w
-
崔庆才丨静觅
MongoDB
Python
- 7.8w
-
Vue.js
Webpack
JavaScript