Android Paging codelab

2018-11-05  本文已影响126人  鹿小纯0831

1、介绍

你要建造什么

在这个代码库中,您从一个示例应用程序开始,该应用程序已经显示了GitHub存储库列表,从数据库加载数据并且由网络数据支持。 只要用户滚动并到达显示列表的末尾,就会触发新的网络请求,并将其结果保存在数据库中。
您将通过一系列步骤添加代码,在您进行时集成Paging库组件。 这些组件在步骤2中描述。

你需要什么

2、设置您的环境

工程下载地址:https://drive.google.com/file/d/19oEydBoR7293J-pu210feqjkTeeQqLal/view
该应用程序运行并显示与此类似的GitHub存储库列表:

您还可以在GitHub上查看codelab。 初始状态位于主分支上,并在解决方案分支上查看解决方案。

3、分页库组件

分页库使您可以更轻松地在应用程序的UI中逐步和优雅地加载数据。
应用程序架构指南提出了一个具有以下主要组件的架构:

Paging库可以处理所有这些组件并协调它们之间的交互,以便它可以从数据源中分页内容并在UI中显示该内容。

此代码库向您介绍了Paging库及其主要组件:

在此代码框中,您可以实现上述每个组件的示例。

4、工程概况

该应用程序允许您在GitHub中搜索其名称或描述包含特定单词的存储库。 将显示存储库列表,按降序排列,基于星号,然后按名称显示。 数据库是UI显示的数据的真实来源,它由网络请求支持。
按名称,通过RepoDao.reposByName中的LiveData对象检索存储库列表。 每当来自网络的新数据插入数据库时,LiveData将再次使用查询的整个结果发出。
当前实现有两个内存/性能问题:

该应用程序遵循“应用程序架构指南”中推荐的体系结构,使用Room作为本地数据存储。 以下是每个包装中的内容:

注意:GithubRepositoryRepo类具有相似的名称,但用途却截然不同。 存储库类GithubRepository与代表GitHub代码存储库的Repo数据对象一起使用。

5、使用PagedList以块的形式加载数据

在我们当前的实现中,我们使用LiveData <List <Repo >>从数据库中获取数据并将其传递给UI。 每当修改本地数据库中的数据时,LiveData都会发出更新的列表。 List <Repo>的替代方案是PagedList <Repo>PagedList是List的一个版本,它以块的形式加载内容。 与List类似,PagedList包含内容的快照,因此当通过LiveData传递PagedList的新实例时会发生更新。
创建PagedList时,它会立即加载第一个数据块,并在将来加载了内容时会再次扩展,。 PagedList的大小是每次传递期间加载的项目数。 该类支持无限列表和具有固定数量元素的非常大的列表。
用PagedList <Repo>替换List <Repo>的出现次数:

viewModel.repos.observe(this, Observer<PagedList<Repo>> {
            showEmptyList(it?.size == 0)
            adapter.submitList(it)
 })

6、定义分页列表的数据源

agedList从源动态加载内容。 在我们的例子中,因为数据库是UI的主要真实来源,它也代表了PagedList的来源。 如果您的应用直接从网络获取数据并在没有缓存的情况下显示数据,则发出网络请求的类将成为您的数据源。
源由DataSource类定义。 要从可以更改的源(例如允许插入,删除或更新数据的源)中分页数据,您还需要实现知道如何创建DataSourceDataSource.Factory。 每当更新数据集时,DataSource都会失效并通过DataSource.Factory自动重新创建。
Room持久性库为与Paging库关联的数据源提供本机支持。 对于给定的查询,Room允许您从DAO返回DataSource.Factory并为您处理DataSource的实现。
更新代码以从Room获取DataSource.Factory:

fun reposByName(queryString: String): DataSource.Factory<Int, Repo>

7、构建和配置分页列表

要构建和配置LiveData <PagedList>,请使用LivePagedListBuilder。 除了DataSource.Factory,您还需要提供PagedList配置,其中包括以下选项:

更新GithubRepository以构建和配置分页列表:
定义要由分页库检索的每页项目数。 在伴随对象中,添加另一个名为DATABASE_PAGE_SIZEconst val,并将其设置为20.然后,我们的PagedList将以20个项目的块为单位从DataSource中分页数据。

companion object {
        private const val NETWORK_PAGE_SIZE = 50
        private const val DATABASE_PAGE_SIZE = 20
}

注意:DataSource页面大小应该是几个屏幕的项目。 如果页面太小,您的列表可能会闪烁,因为页面内容未覆盖整个屏幕。 较大的页面大小有利于加载效率,但可能会增加显示更新的延迟。

GithubRepository.search()方法中,进行以下更改:

// Get data source factory from the local cache
val dataSourceFactory = cache.reposByName(query)

search()函数中,从LivePagedListBuilder构造数据值。 LivePagedListBuilder是使用dataSourceFactory和您之前定义的数据库页面大小构造的。

fun search(query: String): RepoSearchResult {
    // Get data source factory from the local cache
    val dataSourceFactory = cache.reposByName(query)

    // Get the paged list
    val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE).build()

     // Get the network errors exposed by the boundary callback
     return RepoSearchResult(data, networkErrors)
}

8、使RecyclerView适配器与PagedList一起使用

要将PagedList绑定到RecycleView,请使用PagedListAdapter。 每当加载PagedList内容时,PagedListAdapter都会收到通知,然后通知RecyclerView进行更新。
更新ReposAdapter以使用PagedList

class ReposAdapter : PagedListAdapter<Repo, RecyclerView.ViewHolder>(REPO_COMPARATOR)

我们的应用程序最终编译! 运行它,看看它是如何工作的。

9、触发网络更新

目前,我们使用附加到RecyclerViewOnScrollListener来了解何时触发更多数据。 不过,我们可以让Paging库处理列表滚动。
删除自定义滚动处理:

删除自定义滚动处理后,我们的应用程序具有以下行为:

当数据源没有任何更多数据要提供给我们时,会出现问题,因为从初始加载数据返回零项或者因为我们已经从DataSource到达数据的末尾。 要解决此问题,请实现BoundaryCallback。 当这两种情况发生时,该类会通知我们,因此我们知道何时请求更多数据。 由于我们的DataSource是一个由网络数据支持的Room数据库,因此回调告诉我们应该从API请求更多数据。
使用BoundaryCallback处理数据加载:

class RepoBoundaryCallback(
        private val query: String,
        private val service: GithubService,
        private val cache: GithubLocalCache
) : PagedList.BoundaryCallback<Repo>() {
    override fun onZeroItemsLoaded() {
    }

    override fun onItemAtEndLoaded(itemAtEnd: Repo) {
    }
}
// keep the last requested page. 
// When the request is successful, increment the page number.
private var lastRequestedPage = 1

private val _networkErrors = MutableLiveData<String>()
// LiveData of network errors.
val networkErrors: LiveData<String>
     get() = _networkErrors

// avoid triggering multiple requests in the same time
private var isRequestInProgress = false
override fun onZeroItemsLoaded() {
    requestAndSaveData(query)
}

override fun onItemAtEndLoaded(itemAtEnd: Repo) {
    requestAndSaveData(query)
}

在创建PagedList时更新GithubRepository以使用BoundaryCallback:

fun search(query: String): RepoSearchResult {
    Log.d("GithubRepository", "New query: $query")

    // Get data source factory from the local cache
    val dataSourceFactory = cache.reposByName(query)
        
    // Construct the boundary callback
    val boundaryCallback = RepoBoundaryCallback(query, service, cache)
    val networkErrors = boundaryCallback.networkErrors

    // Get the paged list
    val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)
             .setBoundaryCallback(boundaryCallback)
             .build()

    // Get the network errors exposed by the boundary callback
    return RepoSearchResult(data, networkErrors)
}

而已! 使用当前设置,Paging库组件是在正确的时间触发API请求,将数据保存在数据库中以及显示数据的组件。 因此,运行应用程序并搜索存储库。

10、包装

现在我们添加了所有组件,让我们退一步看看一切如何协同工作。
DataSource.Factory(由Room实现)创建DataSource。 然后,LivePagedListBuilder使用传入的DataSource.Factory,BoundaryCallback和PagedList配置构建LiveData <PagedList>。 此LivePagedListBuilder对象负责创建PagedList对象。 创建PagedList时,会同时发生两件事:

上一篇 下一篇

猜你喜欢

热点阅读