初めてのEpoxy

入社して3ヶ月経ちそうですが、まだEpoxyを触ったことなかったので備忘録として書いていきます。
Epoxyを使うとRecyclerViewを簡単に実装できるみたいです！（中毒性があるので注意らしい）

# 実装
##Gradle
まずはEpoxyを使うためにGradleに依存関係を記述します。

```build.gradle
    def epoxy_version = "3.11.0"
    implementation "com.airbnb.android:epoxy:$epoxy_version"
    // databinding使用するなら
    implementation "com.airbnb.android:epoxy-databinding:${epoxy_version}"
    kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
```

## package-info
gradleに依存関係を記述したら、`package-info.java`ファイルをプロジェクト内に作成します。

```package-info.java
@EpoxyDataBindingLayouts({R.layout.recyclerview_item})
package com.example.todo;

import com.airbnb.epoxy.EpoxyDataBindingLayouts;
```

`@EpoxyDataBindingLayouts`アノテーションでレイアウトファイルを指定するとModelが自動生成してくれます。
ここで一旦ビルドをかけてください。

## layout
[過去の記事](https://qiita.com/karass/items/4f2e33446eb0253859ef)のxmlファイルをDataBindingするために`<layout>`属性で囲います。後に書くControllerからTodoTitleを受け取って表示させます。

```recyclerview_item.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"
    xmlns:tools="http://schemas.android.com/tools">

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

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:foreground="?android:attr/selectableItemBackground"
        app:cardElevation="10dp">

        <LinearLayout
            android:id="@+id/LinearLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:background="@android:color/white"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/sampleImg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:contentDescription="@string/recycler_picture"
                app:srcCompat="@mipmap/ic_launcher_round" />

            <TextView
                android:id="@+id/sampleTxt"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="@{ todoTitle }"
                tools:text="TODO"
                android:textSize="30sp" />

        </LinearLayout>
    </androidx.cardview.widget.CardView>
</layout>
```

## Controller
Controllerは引数が一つの時は`TypedEpoxyController<List<Todo>>()`を継承します。

これは引数2つの場合は、`Typed2EpoxyController()`
3つ、`Typed3EpoxyController()`
4つ、`Typed4EpoxyController()`

今回は、表示するリストだけをもらえればいいので、`TypedEpoxyController()`を選択しました。


```EpoxyController.kt
class RecyclerViewController(
    lifecycleOwner: LifecycleOwner,
    viewModel: MainViewModel
) : TypedEpoxyController<List<Todo>>() {
    init {
        viewModel.todoList.observe(lifecycleOwner, ::setData)
    }
    override fun buildModels(data: List<Todo>?) {
        data?.forEach {
            recyclerviewItem {
                id(modelCountBuiltSoFar)
                todoTitle(it.todoTitle)
            }
        }
    }
}
```
順を追ってControllerを見ていきましょう。`buildModels()`は`setData()`が呼ばれると実行されます。そのため今回はRoomに保存しているLiveDataが更新させるたびに`setData()`を呼ぶようにObserveを書きました。

`buildModels()`内に表示させるものを書いていきます。引数でもらったdataを自動生成された拡張関数recyclerviewItemで表示させます。

```kotlin
inline fun ModelCollector.recyclerviewItem(
    modelInitializer: RecyclerviewItemBindingModelBuilder.() -> Unit
) {
    add(
        RecyclerviewItemBindingModel_().apply {
            modelInitializer()
        }
    )
}
```
毎回`add()`を書かずに済むのでとても便利ですね！（これが自動生成されるなんて）
`id()`とは、表示される要素毎に重複しないものを設定して上げる必要があります。これを指定すると表示させる要素の差分などを判別してくれるらしい。とりあえず被らないように`modelCountBuiltSoFar`を入れました。これは、Epoxyで表示させている要素の数です。idの付け方で迷ったらこれにしてます。

## Activity
あとは同じみの記述をするだけでリスト表示ができました！ ViewHolderやらを書かなくて済むのはとてもいい感じです。

```MainActivity.kt
        recyclerViewController = RecyclerViewController(this, mainViewModel)
        main_recycler_view.apply {
            this.adapter = recyclerViewController.adapter
            this.layoutManager = LinearLayoutManager(this@MainActivity)
        }
```