Epoxy入門：複雑な画面をRecyclerViewで楽に作る

## Epoxyとは

EpoxyはRecyclerViewで複雑な画面を構成するためのAndroidライブラリ。
ScrollViewで縦に長い画面をRecyclerViewで楽に管理するのが目的

- Github https://github.com/airbnb/epoxy
- Min SDK 14

## どうやって使う？

主に使うのは`EpoxyModel`と`EpoxyController`
モデルはCustomView、xmlレイアウト(DataBinding)から自動生成される。
そのモデルをコントローラーで並べて宣言するだけ。

今回はKotlin ＋ DataBindingを使う方法を説明する。

## インストール

公式に従ってapp/build.gradleにライブラリを追加
https://github.com/airbnb/epoxy#installation

```gradle:app/build.gradle
dependencies {
    // 以下3行のEpoxyライブラリを追加
    implementation 'com.airbnb.android:epoxy:3.4.2'
    kapt 'com.airbnb.android:epoxy-processor:3.4.2' // Kotlinはkaptを使用
    implementation 'com.airbnb.android:epoxy-databinding:3.4.2'
    ...
```

- 2019/04/30時点では3.4.2が最新
- epoxy-processorはJavaではannotationProcessor、Kotlinではkaptを使うべし
- epoxy-databinding はDataBindingを連携させるなら追加

## EpoxyModelの作り方

今回はDataBindingのみ記載する。

DataBindingの場合、レイアウトxmlから自動でModelを作成するよう記述する。

やる事は以下
- レイアウトxmlをDataBindingで作成
- package-info.java を作成
- 一回ビルド

やることを順に説明する

### レイアウトxmlをDataBindingで作成

表示するViewをいつもどおりxmlで作成する。
DataBinding化することを忘れずに。

今回は`header_view.xml`、`body_view.xml`、`footer_view.xml`を作成した。

https://github.com/ikemura23/Android-Kotlin-Lab/tree/epoxy/app/src/main/res/layout

### package-info.java を作成

`package-info.java` を以下の場所に作る

<img width="325" alt="スクリーンショット 2019-05-01 12.55.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52227/ae2da0b0-d729-7604-5348-3677903f0d31.png">


パス
プロジェクト名/app/src/main/java/package-info.java

内容

```java:package-info.java
@EpoxyDataBindingLayouts({R.layout.header_view, R.layout.body_view, R.layout.footer_view})
package com.ikemura.android_kotlin_lab;

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

`@EpoxyDataBindingLayouts`にレイアウトxmlを指定すると、Modelが自動生成される。

このファイルを作る時、自分で入力して作ろうとするとIDEが認識せずおかしくなった。
本家からコピペで作ったらやっと認識した。
一体なんだったんだろうか

### 一回ビルド

レイアウトxmlとpackage-info.javaを作成したら一度ビルドしておく。
このビルドでDataBindingファイルとEpoxyModelが自動生成される。

自動生成されたEpoxyModelは以下
- HeaderViewBindingModel_
- BodyViewBindingModel_
- FooterViewBindingModel_

みて分かるとおり、`〜BindingModel_` という命名でレイアウトからModelが作られる。

## EpoxyControllerの作り方
２パターンある

### 引数がない時
EpoxyControllerを継承したクラスを作る。
静的画面を作る際に使う

```kotlin
class MainController : EpoxyController() {
    override fun buildModels() {
      ...
    }
```

### 引数があるとき
引数が１つの場合は
`TypedEpoxyController`を継承して、`buildModels`メソッドで引数を受け取る。

```kotlin
class MainController : TypedEpoxyController<DummyContent.DummyItem>() {
    override fun buildModels(data: DummyContent.DummyItem?) {
        
    }
```
引数がの数によって継承するクラスを使い分けるようだ。
２つの場合は `Typed2EpoxyController`
３つの場合は `Typed3EpoxyController`
４つの場合は `Typed4EpoxyController`

と４つまでの引数に対応している。

### 作り方
まずViewを表示するだけの一番シンプルなコード

```kotlin
class MainController : EpoxyController() {
    override fun buildModels() {
        headerView {
            id("header")
        }
        bodyView {
            id("body")
        }
        footerView {
            id("footer")
        }
```

EpoxyControllerを継承し、buildModels内に表示したい順にEpoxyModelを宣言する。
これだけで画面表示は完成している、Epoxyすごい

初見だと上記のControllerを見ても「？？？」となると思うので軽く説明。

#### id()
`id()`はEpoxyが内部でModelを管理するために必要らしい。
渡された値（今回は文字列）を内部でハッシュコード化して、Modelを識別してるようだ。
ちなみにこのidを指定しないと`IllegalEpoxyUsage`というエラーが投げられ、「モデルにidを指定する必要がある」と怒られる。

#### headerView {}
`headerView {}`はなにかというと、Epoxyによって自動生成された拡張関数である。

EpoxyModelKotlinExtensions.kt
が自動生成されており、中身はこんな感じ

```kotlin
inline fun EpoxyController.headerView(modelInitializer: HeaderViewBindingModelBuilder.() -> Unit) {
    HeaderViewBindingModel_().apply  {
        modelInitializer()
    }
    .addTo(this)
}
```
拡張関数内では
`HeaderViewBindingModel_`に引数で渡された関数を実行し（modelInitializer()のとこ）
`addTo(this)`でモデル（HeaderViewBindingModel）にEpoxyControllerを紐づけている。

## 画面に表示

表示を全てEpoxyに任せるなら、レイアウトxmlにはRecyclerViewだけ設置するだけでよい
そのRcyclerViewのAdapterに、EpoxyControllerを渡すだけ



### 表示する画面のレイアウトxml
RecyclerViewだけ設置するだけでOK

```xml:epoxy_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    tools:context=".epoxy.EpoxyFragment"
    />

```

### RecyclerViewにEpoxyControllerを渡す

Activity or Fragment内でEpoxyControllerを渡す

```kotlin
    private fun setupEpoxyController() {
        val controller = MyEpoxyController() // EpoxyControllerのインスタンス化
        recycler_view.adapter = controller.adapter  // adapterを渡す
        controller.requestModelBuild() // データを渡さない場合はrequestModelBuild、渡す場合はsetDataを使う
    }
```

コメントにも書いてあるが、
データを渡さない場合はrequestModelBuild、渡す場合はsetDataに表示するデータ等を渡す


スクショ
![2019-05-01-23-43-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52227/562cb1bf-19d2-a334-fe9a-454dfdb543c8.png)

![2019-05-01-23-44-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52227/d2c35a88-2d67-3f18-eb35-350cd9e2ae57.png)


## Epoxyの感想
初めはViewなのかModelなのかごっちゃになって混乱したが、作っていくと「あーなるほどね」と理解できた。
DataBindingの場合はViewModelのようなクラスが自動生成される、というイメージかな。
（〜BindingModel_ ←こんなクラス）
静的な画面でも縦に長くなる場合はEpoxyを使うと楽になるかも。

RecyclerViewと数十行のEpoxyContorollerだけで画面表示を制御できるのは強力だと思う。

ぜひ自分の担当するプロジェクトにも導入してみたい。

## 参考にしたページ
- 複雑な画面をRecyclerViewで作るEpoxy - Android - まっせぎ blog https://masegi.hatenablog.com/entry/2019/02/24/215211
- EpoxyでRecyclerViewをもっと簡単にする – PicApp Tech Blog https://techblog.picappinc.jp/epoxy%E3%81%A7recyclerview%E3%82%92%E3%82%82%E3%81%A3%E3%81%A8%E7%B0%A1%E5%8D%98%E3%81%AB%E3%81%99%E3%82%8B-9ab0e77b2b5f