DiffUtil + DataBindingでお手軽リスト表示

Android

はじめに

今回はMVVMでRecyclerViewにアイテムを表示させる方法です。

DiffUtilとObservableArrayListを使うことで、リストが変化した際にViewに反映されるようにしました。

実装

手順は以下の通りです。

  1. DiffUtil.Callbackを作る
  2. BindingAdapterを作る
  3. ViewModelにObservableArrayListを持たせる
  4. layoutでバインドする

DiffUtil.Callback

class ResultDiffCallback(val oldItems: List<ResultDisplayItem>, val newItems: List<ResultDisplayItem>) : DiffUtil.Callback() {

    override fun getOldListSize(): Int = oldItems.size

    override fun getNewListSize(): Int = newItems.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItems[oldItemPosition].itemId == newItems[newItemPosition].itemId
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItems[oldItemPosition] == newItems[newItemPosition]
    }
}

BindingAdapter

object RecyclerViewBindingAdapter {
    @JvmStatic
    @BindingAdapter("bind:searchResult")
    fun showSearchResult(view: RecyclerView, items: MutableList<ResultDisplayItem>) {
        val adapter = view.adapter as ResultRecyclerAdapter
        val diff = DiffUtil.calculateDiff(ResultDiffCallback(adapter.mItems, items), true)
        adapter.mItems = items
        diff.dispatchUpdatesTo(adapter)
    }
}

ViewModel

class ResultViewModel {

    val items: ObservableArrayList<ResultDisplayItem> = ObservableArrayList()

    fun searchExec(query: SearchQuery) {
        // APIなどから取得した結果をリストにセット追加する
        items.addAll(response.results)
    }
}

layout

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <data>

        <variable
            name="viewModel"
            type="com.classmethod.app.features.result.ResultViewModel"
            />
    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:searchResult="@{viewModel.items}"
            />
    </FrameLayout>
</layout>

補足

DiffUtilは、リストの比較をしつつ、RecyclerViewへの反映をやってくれる便利なクラスです。

Adapterのリストは自分で更新する必要がありますが、notifyをやってくれます。(下記参照)

public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
    dispatchUpdatesTo(new ListUpdateCallback() {
        @Override
        public void onInserted(int position, int count) {
            adapter.notifyItemRangeInserted(position, count);
        }
        @Override
        public void onRemoved(int position, int count) {
            adapter.notifyItemRangeRemoved(position, count);
        }
        @Override
        public void onMoved(int fromPosition, int toPosition) {
            adapter.notifyItemMoved(fromPosition, toPosition);
        }
        @Override
        public void onChanged(int position, int count, Object payload) {
            adapter.notifyItemRangeChanged(position, count, payload);
        }
    });
}

まとめ

DiffUtilのおかげで、MVVM + RecyclerViewもやりやすくなったと感じます。

最下部スクロールで追加読み込みする画面などで威力を発揮するでしょう。

余談

Android Architecture ComponentsでもViewModelが追加されましたが、GoogleはMVVMを推奨しているんでしょうか・・・?