LoginSignup
8
6

More than 3 years have passed since last update.

Epoxyを使ってRecyclerViewのデータの扱いを楽にする

Last updated at Posted at 2018-04-08

自作アプリでepoxyを使用したので、簡単に使用方法をまとめておきます。

Epoxyとは

Airbib製のライブラリです。RecyclerViewをそのまま使うのに比べ、複数パターンのレイアウトがある場合に簡単に切り替えられたり、表示データの更新でいい感じに差分更新をしてくれます(後述)。
https://github.com/airbnb/epoxy

今回は以下のような構造のデータをリスト表示する例を書いてみます

data class Foo (
    var title: String,
    var bar: List<Bar>
)

data class Bar (
    var body: String,
    var time: String  
)

gradle

build.gradle(app)


apply plugin: 'kotlin-kapt' // kapt使用

android {
 dataBinding {
        enabled = true // data binding使います
    }
}

kapt {
    correctErrorTypes = true
}

dependencies {
    implementation 'com.airbnb.android:epoxy:3.4.2'
    kapt 'com.airbnb.android:epoxy-processor:3.4.2'
    implementation 'com.airbnb.android:epoxy-databinding:3.4.2' // data binding使います
}

cellレイアウトの準備

list_cell_a.xml

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

    <data>
        <variable
            name="title"
            type="String"
            />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{title}"
            android:textSize="24sp"
            android:textStyle="bold"
            tools:text="test"
            />

    </LinearLayout>

</layout>

list_cell_b.xml

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

    <data>

        <variable
            name="body"
            type="String"
            />

        <variable
            name="time"
            type="String"
            />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{body}"
            android:textSize="20sp"
            tools:text="test"
            />


        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{time}"
            android:textSize="18sp"
            tools:text="test"
            />
    </LinearLayout>

</layout>

EpoxyConfig.java

パッケージフォルダのルートにこのファイルを配置することで、ビルド時にレイアウトファイルのクラスを{レイアウトファイルのキャメルケース}BindingModel_の形式で自動生成してくれます。

package <your package name>;

import com.airbnb.epoxy.EpoxyDataBindingLayouts;

@EpoxyDataBindingLayouts({
    R.layout.list_cell_a,
    R.layout.list_cell_b
})
interface EpoxyConfig {}

EpoxyController

AdapterのかわりにTypedEpoxyControllerを継承したクラスを作成します。buildmodelsをoverrideし、レイアウト作成処理を実装します。

class FooBarController : TypedEpoxyController<Foo>() {
    override fun buildModels(foo: Foo) {

        ListCellABindingModel_()
            .title(foo.title) // ←databindingのデータ
            .id(modelCountBuiltSoFar)
            .addTo(this)

        foo.bar.forEach {
            ListCellBBindingModel_()
                    .body(it.body)
                    .time(it.time)
                    .id(modelCountBuiltSoFar)
                    .addTo(this)
        }
    }
}
  • buildModelsメソッドの引数の数に応じて、Typed2EpoxyController、Typed3EpoxyControllerを継承してください(クラス名もっとどうにかならなかったのだろうか)
  • title、body、timeはレイアウトで設定したdatabindingのプロパティです。
  • idは各セルに割り振られるIDです。このidが同じだとセルのデータ更新、idが異なるとセルの新規作成となります。例えば以下のように記載すると、セルは一つしか生成されません。

override fun buildModels(foo: Foo) {

    ListCellABindingModel_()
        .title(foo.title)
        .id("id")
        .addTo(this)

    foo.bar.forEach {
        ListCellBBindingModel_()
                .body(it.body)
                .time(it.time)
                .id("id")
                .addTo(this)
    }
}
  • 特に差分更新を意識する必要がない場合は、idを自動で割り振ってくれるmodelCountBuiltSoFarを使うと良いみたいです

呼び出す画面

呼び出し側のlayoutファイルでは普通にRecyclerViewを宣言します。


class FooBarFragment : Fragment() {
    lateinit var binding: FragmentFooBarBinding //←このフラグメントのレイアウトファイル
    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_foo_bar, container, false)
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        return binding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        val controller = FooBarController()
        binding.recyclerView.adapter = controller.adapter

        val data = Foo("タイトル", listOf(Bar("本文1","時間1"),Bar("本文2","時間2")))
        controller.setData(data)
    })
}
  • controller側のIDを適切に設定していれば、データの登録、更新全てでsetDataを呼べばいい感じにviewに反映してくれます。

所感

  • viewHolderから開放されてスッキリ
  • データ更新時のnotifyから開放されてスッキリ

参考

8
6
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
8
6