Android
RecyclerView
SwipeRefreshLayout
Jetpack
paging

Android Jetpack の Paging を用いたリストを再読み込みするまでの長かった道のり

やりたいこと

PagedListAdapter をセットした RecyclerView を再度読み込みし直したい。

シチュエーション

例:Material Design > Backdrop スタイルの検索結果一覧画面にて

  • 「検索ワード」が変更されたり「絞り込みの条件」が追加された場合に検索結果一覧をリロードしたい
  • 検索結果一覧が Swipe-to-Refresh された場合

など。

backdrop-shrine-ahero.png

試み

PagedListAdapter を再生成して RecyclerView にセットし直すというようなチャチな発想では全然ダメでした :no_good:

参考

公式の googlesamples/android-architecture-components/PagingWithNetworkSample/ を解読します。

Screenshot_1530550075.png

天才・Googler 様が考える実装はクセがスゴかったです。

仕組み

「検索ワード」や「絞り込みの条件」が変化した場合

ViewModel が保持する検索条件に関する MutableLiveData の値を更新して RecyclerView をリフレッシュしています。

Swipe-to-Refresh した場合

ネットワーク上からデータを取得していれば DataSource.invalidate() を呼び、Room からデータを取得していればネットワーク上からデータを取り直しトランザクション終了後自動的に反映されます。

実践

2018 年 7 月 8 日時点の公式のサンプルは「検索ワード」のみが RecyclerView をリフレッシュするトリガーになっていますが、「検索ワード」と「絞り込みの条件」が変化した場合に検索結果一覧をリロードする方法について考えてみました。

サンプル

  1. 複数ある検索条件を1つにまとめたデータクラス SearchParameter を定義
  2. ViewModelSearchParameterMutableLiveData を保持
  3. 検索条件が変化して検索結果一覧を更新したい場合は MutableLiveData.setValue() を実施します
SearchParameter.kt
/**
 * 複数の検索条件をまとめたデータクラス
 */
data class SearchParameter(
        var keyword: String?, // キーワード
        var minPrice: Int?,   // 価格の下限
        var maxPrice: Int?,   // 価格の上限
        var size: Int?        // 大きさ
)
SearchViewModel.kt
class SearchViewModel(private val repository: SearchRepository) : ViewModel() {

    private val searchParameterMutableLiveData = MutableLiveData<SearchParameter>()

    private val repoResult = map(searchParameterMutableLiveData) { searchParameter ->
        repository.search(searchParameter = searchParameter, pageSize = 20)
    }

    val searchResults = switchMap(repoResult) { it.pagedList }!!

    /**
     * 条件を指定して検索を実施
     */
    fun search(keyword: String?, minPrice: Int?, maxPrice: Int?, size: Int?) {
        searchParameterMutableLiveData.value = SearchParameter(
                keyword = keyword,
                minPrice = minPrice,
                maxPrice = maxPrice,
                size = size)
    }
}