整合 Android Paging Library: Part 1

今天我們來介紹如何使用 Paging Library,以及看這個套件是如何幫助我們更快地開發列表顯示的需求。

使用情境和困境

現今大多數的 App 幾乎都會有呈現列表的需求,而列表多半都包含大量的資料,為了效能和節省流量的考量,我們通常都會將列表分割成好幾頁、限制一頁在一定的小數量回傳顯示出來。

這篇會假設你已經知道如何使用 RecyclerView 和 Adapter 顯示列表資料,我們不會針對這部分多做其他說明,只會講解如何把 Paging Library 整合到現在 App 列表。

這樣的列表需求在 Android Paging Library 出現之前我們會用 RecyclerView  搭配判斷是否滑到列表最底部和載入機制來實作(以舊的 MVP 架構為例):

一開啟頁面時,需要先載入第一頁的資料 loadFirstPage(),然後  RecyclerView 需要判斷目前滑動到哪裡,如果滑到列表最底,則要觸發載入下一頁資料。

如果我們 App 有很多地方要顯示列表時,我們可以考慮實作一個 Custom RecyclerView 來把這樣的邏輯包進去,不過這樣的方式不夠優雅,因為當我們的列表分頁有不同的載入機制,或者我們想要提前在滑到底之前的前4筆資料顯示時,就觸發下一頁載入,Custom RecyclerView 未必會是一個很好的解法。

在 Google IO 2018 年推出 Paging Library 後,我們就可以省去這樣的判斷的邏輯,也讓我們有更靈活的分頁載入時機和機制,我們就來看看如何使用 Paging Library 以及實作和架構上會有什麼調整。

Paging Library 原理介紹

首先先來看看 Paging 如何運作,當我們要使用 Paging 時,我們會先把 RecyclerView 所用到的 Adapter 換去繼承 PagedListAdapter(DiffUtils),且在 onBindViewHolder()做資料綁定時呼叫 getItem(position) 來取得目前位置的資料,這時候 PagedListAdapter 就會知道現在已經在列表的什麼位置,以及決定是否要觸發下一頁載入。

PagedListAdapter 知道該載入下一頁的時候,會通知內部的 PagedList 去向 DataSource 要更多資料,而 DataSource 有實作資料取得的方式,所以會呼叫對應的 callback 方法開始在背景載入下一個分頁的資料,成功載入後會通知 PagedList,PagedList 會用 PagedListAdapter 產生時傳入的 DiffUtils 計算新舊列表的差異,然後在 UI Thread 呼叫對應 PagedListAdapter.notifyDataChange()來顯示新資料,整體流程圖就會像這樣:

這樣的運作機制已經帶出了 Paging 三個最重要的元件:1. PagedListAdapter 2. PagedList 3. DataSource。

PagedListAdapter

這類別繼承了 RecyclerView.Adapter,用來顯示 PagedList,建立時需要傳遞一個 DiffUtils 的實作,好讓 Paging 載入更多資料時,可以用來比較差異後顯示新資料(計算差異會在背景執行)。 當我們要整合 Paging 到既有專案時,只需要把原本繼承的 RecyclerView.Adapter 換成 PagedListAdapter 即可。

PagedList

這是一個 List 的封裝類別,用來儲存分頁載入的資料列表,並且通知 DataSource 何時要去載入資料 (第一頁和後面分頁) ,資料載入的動作會在背景執行。我們也可以透過 PagedList.Config 來設定一次載入的筆數、提前多少就執行預先載入的動作 (即可上圖的 prefetchSize ),以及是否使用 Placeholder

DataSource

這類別負責實作資料載入的方法,這邊我們要考慮列表資料要怎麼來、分頁如何載入,Paging 支援三種資料架構:1. Remote only 2. Local only 3. Remote + Local,這篇我們會先介紹最常見、也是最簡單的「1. Remote only」資料架構,Part 2. 會再介紹 3. Remote + Local。

這裡我也找了一張解釋蠻清楚的 GIF 動畫圖,可以看出資料的流向:

Source: https://medium.com/mindorks/pagination-using-paging-library-with-rxjava-and-dagger-d9d05dbd8eac

範例需求

我們沿用之前 文章 所使用的社群 App 當作範例,我們現在要顯示貼文動態牆,會向 Backend 拉列表資料,並且做分頁顯示貼文資料。

設計架構

我們會採用 MVVM 當作 App 架構,那 Paging 現在也支援 RxJava,所以我們會採用 Paging 搭配 RxJava 來實作,整體架構如下圖:

API Request & Response

當我們呼叫不帶任何參數的 /feed API 時,會回傳第一頁的列表資料,同時在 Link header 裡面會包含前後分頁的連結,這樣機制是參考 Github API Pagination,有興趣的可以去看他們的文件。我們前後分頁是由當前頁面 API 而得,而不是我們 client 端自行計算分頁參數 p=N,然後組出下一頁的網址。

實作

1. API & Model Class

首先我們要定義我們的資料來源,先宣告資料類別和 API 介面:

2. DataSource

再來要定義 DataSource,在 Paging 有三種 DataSource 的實作種類:

  1. PageKeyedDataSource:當你的前後一頁的取得方式會從當前分頁而得知,我們現在的情境就是這種情況。
  2. PositionalDataSource:你可以在資料來源的任意位置載入分頁列表。
  3. ItemKeyedDataSource:你必須由第 N 筆資料去載入第 N + 1 筆資料。

這邊因為我們下一頁的位置資訊是從當前的 API 來的,所以我們是實作 PageKeyedDataSource,這個類別我們要實作兩個方法 getItem(position)loadInitial(params: LoadInitialParams<Key>, callback: LoadInitialCallback<Key, Value>)getItem(positionload)loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>) 分別代表第一頁和後續下一頁資料取得的實作。

3. Repository

在 Repository,我們要指定 DataSource 提供資料加上 PagedConfig 分頁設定來建立出 PagedList 的 Observable。 這邊我們還沒提到 PagedList.Config,這是一個類別可以讓我們設定分頁相關的組態,其中包含了:

  1. Page size:一個分頁要載入幾筆資料,如果你的 API 已經有限制,則這個參數則無作用。
  2. PrefetchSize distance:指定多久之前要啟動分頁載入的時機。
  3. Enable placeholder:資料還沒載入時,是否要顯示 placeholder,等資料載入後再替換顯示真正的資料,下面會更詳細的講解。
Prefetch Size distance

Placeholder

假設我們現在在第一頁,當我們第二頁資料還沒載入時,我們列表可能只直接呈現第一頁的10筆資料,而如果我們已經知道我們列表總共是50筆資料,我們是可以讓我們的介面直接顯示總長為50的列表,可是第二頁後面的資料都還沒載入那要顯示什麼?這時候就是 Placeholder 上場的時機,使用 Placeholder有什麼優缺點呢?

  • 優點:我們可以顯示完整的列表,資料會在滑到的時候載入,也不需要在載入下一頁的時候顯示 Progress。
  • 缺點:因為要預先顯示整個完整列表長度,所以表示要預先知道列表總長度,這可能做不到(無限長)或者需要很耗時的計算(計算數量是耗時的工作)。

如果我們要使用 Placeholder (enablePlaceholder = true),必須要在 PagedListAdapter.onBindViewHolder() 處理 getItem(position) == null 的情況,因為當資料還尚未載入的時候,getItem(position) 會回傳 null。

4. View & ViewModel

View 裡面包含 RecyclerView,我們可以看到完全不再需要偵測是否滑動到最底然後啟動載入下一頁的機制,我們只需要訂閱 PagedList 的 Observable,然後把收到的列表透過 PagedListAdapter.submit(pagedList) 傳給Adapter,後續判斷和分頁載入 paging 都會幫你做掉。

總結

Android Paging 封裝了許多分頁載入的邏輯,可以讓現有的 App 做比較無痛的轉換升級,各元件的職責分離,讓 Paging Library 可以輕易套用在各種不同的架構(例如:MVP / MVVM / Clean … 等),接下來 Part 2 將會介紹第二種常見的資料架構 Remote + Local。

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑