9
4

More than 3 years have passed since last update.

【Android】Epoxyのモデル変更判定

Posted at

はじめに

RecyclerViewを超絶簡単に実装できるEpoxyだけど、こいつを使い初めてから幾度となく値を変更しても見た目が変更されない不具合に悩まされ続けてきた。
その度になんとなく他の部分と同じように修正してどうにかしてきたけど、先日やっと根本的な原因がわかった。
もはや自分では忘れないと思うけど、何故か調べても同じようなことで悩んでいる人の記事とか出てこなかったので紹介する。

やりたいこと

選択したやつの色を変えてあげたい。

SNSのいいねボタンとか想像してもらえるとわかりやすいと思う。
押すたびにステータスが変わって、それに応じて見た目も変化して欲しい。

実装方法

特別な処理は入れていないけど、一応のっけとく。

◾︎SampleActivity

class SampleActivity : AppCompatActivity() {

    private lateinit var binding: ActivitySampleBinding
    private var epoxyController: SampleEpoxyController? = null
    private var sampleEpoxyModels: List<SampleEpoxyModel>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_sample)
        setController()
    }

    private fun setController() {
        sampleEpoxyModels = (0..9).map {
            SampleEpoxyModel(it)
        }
        epoxyController = SampleEpoxyController(
            onClick = {
                val sampleEpoxyModel = sampleEpoxyModels?.find { sampleEpoxyModel ->
                    sampleEpoxyModel.id == it.id
                }
                sampleEpoxyModel?.isSelected = sampleEpoxyModel?.isSelected?.not() ?: false
                epoxyController?.setData(sampleEpoxyModels)
            }
        )
        binding.recyclerView.setController(epoxyController as EpoxyController)
        epoxyController?.setData(sampleEpoxyModels)
    }
}

◾︎SampleController

class SampleEpoxyController(
    private val onClick: (SampleEpoxyModel) -> Unit
) : TypedEpoxyController<List<SampleEpoxyModel>?>() {

    override fun buildModels(data: List<SampleEpoxyModel>?) {
        data?.forEach { sampleEpoxyModel ->
            itemSampleList {
                id("EpoxyModel_id:${sampleEpoxyModel.id}")
                sampleEpoxyModel(sampleEpoxyModel)
                onClick { _ ->
                    onClick.invoke(sampleEpoxyModel)
                }
            }
            itemDivider {
                id("divider")
            }
        }
    }
}

◾︎ activity_sample.xml

<?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">

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <com.airbnb.epoxy.EpoxyRecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:splitMotionEvents="false"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toBottomOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

◾︎ item_sample_list.xml

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

    <data>

        <variable
                name="sampleEpoxyModel"
                type="com.kl.testapp2.epoxymodelchange.SampleEpoxyModel" />

        <variable
                name="onClick"
                type="android.view.View.OnClickListener" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="@{sampleEpoxyModel.isSelected ? @color/colorPrimary : @color/white}"
            android:onClick="@{onClick}" />

</layout>

リストに設定したonClickのイベントを受け取って、Activityが保持しているListに対してfindして更新するアイテムを見つける。
取得したアイテムのフラグを反転させて再度setData()を実行している。

不具合内容

中の値を入れ替えて、しっかりsetDataしているのに見た目が変わらない。

sampleEpoxyModel?.isSelected = sampleEpoxyModel?.isSelected?.not() ?: false
epoxyController?.setData(sampleEpoxyModels)

更新後の値を確認してもやっぱりきちんと変わっている。
一応idを変えればViewHolderごと再生性できるんだけど、それをするとちらついて鬱陶しい。

原因&解決方法

色々試した結果、リストのフラグは変わっていても、インスタンス自体は同じなのが原因だということがわかった。
どうやらEpoxyは、保持しているリストの中の値が変わったかどうかは見ていないようで、参照しているインスタンスが変更されたかどうかで変更を検知してるみたい。
==equals()の違いだね。

ということは、変更する要素のindexに対して.copy()で新たに生成したインスタンスを入れてあげれば変更が検知されるはず。

onClick = {
    val index = sampleEpoxyModels?.indexOf(it) ?: -1
    sampleEpoxyModels?.set(
        index,
        it.copy(
            isSelected = it.isSelected.not()
        )
    )
    epoxyController?.setData(sampleEpoxyModels)
}

こんな感じで修正したら、きちんと動きました!!

まとめ

Epoxyでフラグ使ってViewの見た目を更新するときはインスタンスごと再生成してあげましょう!

おわり

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4