RecyclerViewにListAdapterを使って差分更新を実現する
TL;DR
RecyclerViewを用いた画面で、ListAdapterを使用して差分更新を実現したので方法をまとめます。
ListAdapterとはなにか?
下記のリンクがListAdapter
リファレンスです。
https://developer.android.com/reference/android/support/v7/recyclerview/extensions/ListAdapter
パッケージはandroid.support.v7.recyclerview.extensions
となり、Support Library 27.1.0から実装されています。
ListAdapter
はAsyncListDiffer
をラップしたRecyclerView.Adapter
の実装です。
AsyncListDiffer
は与えられたList
の差分更新をバックグラウンドスレッドで実行し、差分が存在する場合、RecyclerView.Adapter
の更新通知(notifyItemRangeInserted
など)の呼び出しを行います。
なにが嬉しいのか?
AACのViewModelとLiveDataを持ちいたMVVMのアーキテクチャを採用した場合、ViewModel側にLiveData<List<Hoge>>
のような形でリスト要素をプロパティとして持たせた上でView側(Activity/Fragment)でLiveDataを監視、変更があれば変更されたListを用いてRecyclerViewを更新という実装をするかと思います。
このときに、単純にRecyclerView.Adapter
に対して更新されたListを渡した場合、すべての要素が更新されますが、ListAdapter
を使用した場合は新しく取得したListと前回ListAdapter
に設定したListの差分となる部分のみRecyclerViewの更新が行われる形となり、更新処理の高速化が見込まれます。
DiffUtil.ItemCallbackの実装
まず、ListAdapter
を使用するためにはListに含まれる要素を比較する為のDiffUtil.ItemCallback<ITEM>
の実装が必要なので、そちらを実装します。
DiffUtil.ItemCallback<ITEM>
の定義は以下を参照ください。
https://developer.android.com/reference/android/support/v7/util/DiffUtil.ItemCallback
実装すべきメソッドは以下の2つになります。
areContentsTheSame(T oldItem, T newItem)
oldItemとnewItemの値が同じ場合にtrue
areItemsTheSame(T oldItem, T newItem)
oldItemとnewItemが同じ要素(DBのカラムなど)を参照している場合にtrue
実装例は以下のようになります。
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Book>() { override fun areContentsTheSame(oldItem: Book?, newItem: Book?) = oldItem == newItem override fun areItemsTheSame(oldItem: Book?, newItem: Book?) = oldItem?.id == newItem?.id }
ListAdapterの実装
次にListAdapter
自体の実装ですが、こちらはコンストラクタに実装済みのDiffUtil.ItemCallback
インスタンスを渡すようにすればOKです。
class MyAdapter(val inflater: LayoutInflater) : ListAdapter<MyItem, MyItemViewHolder>(DIFF_CALLBACK) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) { … } override fun onBindViewHolder(holder: MyItemViewHolder, position: Int) { holder.bindTo(getItem(position)); } }
Activity/Fragmentからの呼び出し
最後にActivity/FragmentからAdapterに値を反映させる場合はsubmitList()
の呼び出しを行います。
viewModel.itemList.observe(this, Observer{ adapter.submitList(it) })