头图

Welcome to read Paging 3.0 of MAD Skills series In this article, I will introduce Paging 3.0 and focus on how to integrate it into the data layer of your application. If you prefer a video about this content, please here view.

Why use Paging 3.0?

Presenting a column of data to the user is one of the most common UI patterns. When you need to load a large amount of data, you can obtain/display data asynchronously in blocks to improve application performance. This pattern is so common, if there are dependent libraries that can provide abstractions that facilitate the realization of this pattern, it will bring great convenience to developers. This is the use case that Paging 3.0 strives to solve. As an additional benefit, it also allows your application to support an unlimited data collection; and if your application loads data over the network, it also provides convenience for supporting local caching.

If you are using Paging 2.0, then Paging 3.0 also provides a series of improvements to the features included in its predecessor:

  • Priority support for Kotlin coroutines and Flow.
  • Supports asynchronous loading via RxJava Single or Guava ListenableFuture primitives.
  • Provides built-in loading status and error signals for responsive UI design, including retry and refresh functions.
  • Improve the warehouse layer, including support for cancellation and simplified data source interface.
  • Improve the presentation layer, list separator, custom page transition, and loading status header and footer.

For more information, please refer to the migration document Paging 2.0 to Paging 3.0.

insert data

In the architecture scheme of your application, Paging 3.0 is most suitable as a way to obtain data from the data layer and transmit the data in the UI layer ViewModel In Paging 3.0, we PagingSource , which defines how to get and refresh data PagingConfig

PagingSource Map , both of which need to define two generic types: the type of the paged Key and the type of the loaded data. For example, getting the PagingSource Repo project from the Github API-based page can be defined as:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

class GithubPagingSource(
    …
) : PagingSource<Int, Repo>()

△ PagingSource statement

A fully functional PagingSource needs to implement two abstract methods:

  1. load()
  2. getRefreshKey()

load method

load() method, as its name suggests, is called by the Paging library to asynchronously load the data to be displayed. This method will be called upon initial loading or in response to the user sliding to the boundary. The load method will pass in a LoadParams object, you can use it to determine how to trigger the call of the load method. This object contains information about the load operation, including:

  • Key of the page to be loaded: If this is the first time the load method is called (initial load), LoadParams.key will be null . In this case, you must define the initial page Key.
  • Load size: The number of items to be loaded in the request.

The return type of the load method is LoadResult . it can be:

  • LoadResult.Page : Targeting successfully loaded.
  • LoadResult.Error : Failed to load.
/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */   

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
        val position = params.key ?: GITHUB_STARTING_PAGE_INDEX
        val apiQuery = query + IN_QUALIFIER
        return try {
            val response = service.searchRepos(apiQuery, position, params.loadSize)
            val repos = response.items
            val nextKey = if (repos.isEmpty()) {
                null
            } else {
                // 初始加载大小为 3 * NETWORK_PAGE_SIZE
                // 要保证我们在第二次加载时不会去请求重复的项目。
                position + (params.loadSize / NETWORK_PAGE_SIZE)
            }
            LoadResult.Page(
                data = repos,
                prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
                nextKey = nextKey
            )
        } catch (exception: IOException) {
            LoadResult.Error(exception)
        } catch (exception: HttpException) {
            LoadResult.Error(exception)
        }
    }

△ load method implementation

Note that by default, the initial load size is three times the page size. This ensures that when the list is loaded for the first time, even if the user scrolls slightly, enough data can be seen, so as to avoid triggering too many network requests. This is also something that needs to be considered when calculating the next Key PagingSource

getRefreshKey method

The refresh key is used for the PagingSource.load() method (the first call is the initial load, and the initial key provided for Pager is used). Whenever the Paging library wants to load new data to replace the current list (for example, pull-down refresh or database update, configuration changes, process termination, etc., the data becomes invalid), a refresh operation will occur. In general, the follow-up call to refresh want to reload to PagingState.anchorPosition centric data, and PagingState.anchorPosition represents the index position recently visited.

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

   // 刷新 Key 用于在初始加载的数据失效后下一个 PagingSource 的加载。
    override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
        // 我们需要获取与最新访问索引最接近页面的前一个 Key(如果上一个 Key 为空,则为下一个 Key)
        // anchorPosition 即为最近访问的索引
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }

△ getRefreshKey method implementation

Pager Object

After defining PagingSource , we can now create Pager . Pager class is responsible for incrementally pulling data sets PagingSource according to the request of the UI. Since Pager needs to access PagingSource , it is usually created in the data layer that PagingSource

Another class required to construct Pager PagingConfig , which defines the Pager obtains data. In addition to the required pageSize parameters, PagingConfig also exposes many optional parameters, you can use them to fine-tune the behavior of Pager

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

private const val NETWORK_PAGE_SIZE = 30

class GithubRepository(private val service: GithubService) {

    fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {
        Log.d("GithubRepository", "New query: $query")
        return Pager(
            config = PagingConfig(
                pageSize = NETWORK_PAGE_SIZE,
                enablePlaceholders = false
            ),
            pagingSourceFactory = { GithubPagingSource(service, query) }
        ).flow
    }
}

△ Create Pager

A brief description of the parameters used in the code for PagingConfig above is as follows:

  • pageSize : The number of items to PagingSource each time.
  • enablePlaceholders : Do you need PagingData to return null for data that has not been loaded.

Usually we want pageSize be large enough (at least enough to fill the visible area of the interface, but preferably 2 to 3 times this amount), so that Pager does not have to be scrolled by the user in order to display enough content on the screen Get data over and over again during operation.

Get your data

Pager type produced is PagingData , this type provides access behind PagingSource different windows. When the user scrolls through the list, PagingData will continue PagingSource to provide content. If PagingSource fails, Pager will issue a new PagingData to ensure that the paged items are synchronized with the content displayed in the UI. It may be helpful for your understanding to PagingData PagingSource at a certain time node.

Since PagingSource is PagingSource fails, the Paging library provides a variety of ways to use PagingData in the form of streams:

  • Kotlin Flow through Pager.flow
  • LiveData through Pager.liveData
  • RxJava Flowable through Pager.flowable
  • RxJava Observable through Pager.observable

PagingData can be manipulated and converted ViewModel before displaying the paging item to the UI.

followed by

Following the above steps, we have integrated Paging 3.0 into the data layer of your application! How to consume PagingData in the UI and populate our warehouse list, please pay attention to our follow-up articles.

Welcome to Click here to submit feedback to us, or share your favorite content or problems found. Your feedback is very important to us, thank you for your support!


Android开发者
404 声望2k 粉丝

Android 最新开发技术更新,包括 Kotlin、Android Studio、Jetpack 和 Android 最新系统技术特性分享。更多内容,请关注 官方 Android 开发者文档。