Anvil - Reactライクな差分レンダリングをAndroidでやってみる

この記事は[WanoグループAdventCalendar2016](http://qiita.com/advent-calendar/2016/wano-group)の10日目の記事になります。
今の所ちゃんと埋まっていてホッとしています。(ちょっと遅れた)

## 書くこと
本稿では、Androidのネイティブ開発用のViewライブラリ、[Anvil](https://github.com/zserge/anvil)について取り扱います。
AnvilはWebフロントエンドでの仮想DOMライブラリ、Reactやmithril.jsにインスパイアされています。
個人的にはDataBindingなどの記法より好きで(あれをいじってるとなんかAngular1系のこと思い出してウッ...てなる)、なかなか便利なライブラリですが、日本語での情報を見かけないのでご紹介しようと思います。

ReactNativeに移行するほどではないけど、Reactの使い勝手をネイティブアプリ開発で求めている方にオススメです。


## Anvil概要

[Anvil](https://github.com/zserge/anvil) はAndroidのモバイルアプリ開発用のライブラリでありながら、webフロントエンド界隈でいう仮想DOMの"差分変更"という考え方にインスパイアされています。

リポジトリのページや[anvil.site](http://anvil.site/) や　[example](https://github.com/zserge/anvil-examples) 、[作者さんのブログ?](http://zserge.com/blog/anvil-2.html)　を見ればたいていの用法はわかるかと思うのですが、いくつか使い方をご紹介します。
導入が恐ろしく簡単です。

Kotlinでももちろん使えますが、今回は Java + retolambda でコードを記述してみます。

## Componentのimport

最低限anvil-sdk本体をimportすれば使えますが、他にもいろいろ揃ってます。
build.gradle

```
compile 'co.trikita:anvil-sdk15:0.4.0'
compile 'co.trikita:anvil-appcompat-v7:0.4.0'
compile 'co.trikita:anvil-support-v4:0.4.0'
compile 'co.trikita:anvil-design:0.4.0'
compile 'co.trikita:anvil-recyclerview-v7:0.4.0'

```

importする

```

import trikita.anvil.Anvil;
import static trikita.anvil.DSL.*;
import static trikita.anvil.appcompat.v7.AppCompatv7DSL.*;
// ... 自由

```

## Stateless Component

関数的に走る、状態を持たないviewの記述法。
たいていの場合はこれだけで済むかなあと思います。


```java


Anvil.mount(findViewById(R.id.anvil_content) , ()->{
    relativeLayout(()->{
        size(FILL,FILL);

        init(()->{
            //初回だけなんかするときはここ
        });

        textView(()->{
            id(R.id.dummy_id_1);
            size(WRAP,WRAP);
            textSize(R().getDimensionPixelSize(R.dimen.my_big_text_size));
            centerHorizontal();
            centerVertical();
            textColor(R().getColor(R.color.white));

            if (myData.text != null){
              text(myData.text);
            } else {
              text("HogeHoge");
            }
        });
    });
});

myData.text = "FugaFuga"; //モデルの変更
Anvil.render(); //差分再計算

```


## Stateful Component
一時的にコンポーネント内に情報を保持しておきたい場合やなど、自前のロジックでViewの状態を管理する必要があるときは

```java

public class MyView extends RenderableView {
    public MyView(Context c) { super(c); }
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        // Initialize your view component
    }
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // De-initialize your view component
    }
    public void view() { /* your layout here */ }
}

```
こんな感じで　RenderableView　を継承して使います。

### 独自XMLやサードパーティのView　
ライブラリで定義されていないViewをさっくり当てる方法もあります。
ここでは、CSSのFlexBoxレイアウトに慣れてる方であれば超絶便利なGoogle先生の[flexbox-layout](https://github.com/google/flexbox-layout) を当ててみます。

```java

v(FlexboxLayout.class , ()->{

    init(()->{
        FlexboxLayout flexboxLayout = Anvil.currentView();
        flexboxLayout.setJustifyContent(FlexboxLayout.JUSTIFY_CONTENT_SPACE_BETWEEN);
        flexboxLayout.setFlexDirection(FlexboxLayout.FLEX_DIRECTION_ROW);
    });

    for (ChildModel childModel : childModels){
        textView(()->{
            size(WRAP ,WRAP);
            text(childModel.text);
        });
    }
});

```

これでカスタムViewも使えるようになりました！

## カスタマイズAttribute
上記の例ではinit();内で無理やりカスタムビューのセッターを叩いていましたが、もちろんこれでは初めの1回しか適用されません。
ここでは、Supportv4DSLの内容を参考に、画像ロードライブラリPicassoの入力差分を管理する関数を書いてみます。


```java 
private static final class PicassoFunc implements Anvil.AttrFunc<String> {
    public static final PicassoFunc instance = new PicassoFunc();

    public void apply(View v, final String newValue, final String oldValue) {
        if (v instanceof ImageView) {
            if (newValue == null){
                return;
            }

            if (newValue.equals(oldValue)){
                return;
            }

            Picasso.with(v.getContext())
                    .load(newValue)
                    .into((ImageView) v);

        }
    }
}

public static Void picasso(String arg) {
    return BaseDSL.attr(PicassoFunc.instance, arg);
}


```

これで,

```java 
imageView(()->{
    size(FILL,FILL);
    picasso("https://???.png");
    
});

```

のように、Picassoの差分をとる関数ができました。



## 結び
いくつか使用方法をご紹介しました。

Flux/Redux系のデザインパターンとの相性も非常にいいのではないでしょうか。
もちろん、がっつりフレームワークとして使わなくとも、ちゃちゃっとviewを動的に当てたい、などの用途に関しても非常に有用かと思いますので、
この際に試してみてください。


明日のwanoグループのアドベントカレンダーはbbq-allstars氏の「アプリのCIについて」です。