コレクション画面(=なにかしらの一覧画面)を実装する際にRecyclerViewをよく使うと思います。
僕としては複雑なViewTypeを比較的簡単に作り分けるために、Epoxyのような便利なライブラリを使うことが多いのですが、弊社の新人研修の課題で久しぶりにAdapterを書く機会があったので、その時の実装をメモ程度に残しておきます。
差分を比較する
どの値を比較して、差分とみなすかを定義します。
下記では単に比較対象の equals
に従って比較しています。
MyDiffable.kt
interface Diffable {
// otherと同じIDを持つかどうか
fun isTheSame(other: Diffable) = equals(other)
// otherと完全一致するかどうか
fun isContentsTheSame(other: Diffable) = equals(other)
}
private class Callback(
val old: List<Diffable>,
val new: List<Diffable>
) : DiffUtil.Callback() {
override fun getOldListSize() = old.size
override fun getNewListSize() = new.size
override fun areItemsTheSame(
oldItemPosition: Int,
newItemPosition: Int
) = old[oldItemPosition].isTheSame(new[newItemPosition])
override fun areContentsTheSame(
oldItemPosition: Int,
newItemPosition: Int
) = old[oldItemPosition].isContentsTheSame(new[newItemPosition])
}
fun calculateDiff(
old: List<Diffable>,
new: List<Diffable>,
detectMoves: Boolean = false
): DiffUtil.DiffResult = DiffUtil.calculateDiff(Callback(old, new), detectMoves)
Entityの実装
Diffableを継承したEntityをつくります。
data classにはDiffableの差分計算に必要なequalsメソッドが既に定義されているので、それをそのまま使います。
HogeItem.kt
data class HogeItem(
val id,
val name
) : Diffable
Adapterの実装
Viewの余計な更新が行われるのを防ぐために、デリゲートで差分を計算してから更新を伝えるようにします。
HogeAdapter.kt
class HogeAdapter(
lifecycleOwner: LifecycleOwner,
private val viewModel: HogeViewModel
) : RecyclerView.Adapter<HogeAdapter.ViewHolder>() {
private var items: List<HogeItem> by Delegates.observable(emptyList()) { _, o, n ->
calculateDiff(o, n).dispatchUpdatesTo(this)
}
init {
viewModel.hogeList.observe(lifecycleOwner) { items = it }
}
override fun getItemCount() : Int { /* ... */ }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { /* ... */ }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { /* ... */ }
class ViewHolder(val binding: HogeItemBinding) : RecyclerView.ViewHolder(binding.root)
}
利用側の実装
LiveDataの監視はAdapter側で行っているので、ViewではRecyclerViewにAdapterを設定するだけでよくなります。
HogeActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.hogeRecyclerView.adapter = HogeAdapter(this, viewModel)
}
HogeFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.hogeRecyclerView.adapter = HogeAdapter(viewLifecycleOwner, viewModel)
}
まとめ
APIで取得したデータを必要最低限のViewの更新で反映できるので、大規模なコレクションではパフォーマンスの向上やUXの改善に繋がります。