Androidでkotlin, DataBinding, RxJava, RxPropertyを利用してMVVMな感じでfizz buzz する

この記事は、[スタートトゥデイ工務店 Advent Calendar](http://qiita.com/advent-calendar/2016/stk) 15日目の記事です。

# はじめに
最近RxとかDataBindingが流行ってきている空気をひしひしと感じるので、このビッグウェーブに乗り遅れないためにfizz buzzします。

# 動作仕様
+ ボタンをタップした回数を表示する
+ ボタンがタップされた回数が3で割り切れるなら fizz のViewを表示する。割り切れないなら非表示にする
+ ボタンがタップされた回数が5で割り切れるなら buzz のViewを表示する。割り切れないなら非表示にする

下記の3パターンでやってみました。
1. 今までの延長上にRxを取り入れるパターン
2. ストリームの分岐をつかって、Rxっぽくしてみるパターン
3. [RxProperty](https://github.com/k-kagurazaka/rx-property-android)を利用して、Bindingしてる感を高めたパターン

では順番にやっていきます。

# 実装
## 今までの延長上にRxを取り入れるパターン
[MainActivity.kt](https://github.com/kyanro/AndroidRxFizzBuzz/blob/488a0e68d9ee729345ab01d5d3bf1fcbfa96e973/app/src/main/kotlin/kyanro/com/androidrxfizzbuzzsample/MainActivity.kt)
[activity_main.xml](https://github.com/kyanro/AndroidRxFizzBuzz/blob/488a0e68d9ee729345ab01d5d3bf1fcbfa96e973/app/src/main/res/layout/activity_main.xml)

Rxを利用した部分は下記の通り。今までの延長上、と感じる部分は

+ 一つのストリームだけを利用して、subscribe時にタップ数から表示、非表示の判定をしている部分

```kotlin
init {
    buttonTapStream
            .map({ event -> 1 })
            .scan({ sum, n -> sum + n })
            .subscribe({ n: Int ->
                count = n.toString()
                activity.setFizzEnabled(n % 3 == 0)
                activity.setBuzzEnabled(n % 5 == 0)
            })
}
```

## ストリームの分岐をつかって、Rxっぽくしてみるパターン
1の実装が特に悪いというわけではないのですが、せっかくRxを利用するのでそれっぽい形で実装してみます。
[MainActivity.kt](https://github.com/kyanro/AndroidRxFizzBuzz/blob/89b903a71d9948d583d8a03d2521d3b27ec54887/app/src/main/kotlin/kyanro/com/androidrxfizzbuzzsample/MainActivity.kt)
[activity_main.xml](https://github.com/kyanro/AndroidRxFizzBuzz/blob/89b903a71d9948d583d8a03d2521d3b27ec54887/app/src/main/res/layout/activity_main.xml)

Rxっぽいかなと感じる部分は、各ビュー用にそれぞれStreamを分岐させているあたりです。
また、その結果をDataBindingの機能を利用して直接ViewにBindできるところが個人的には高ポイントです。(今回はfizz,buzz用に準備したViewのVisibilityにBindしています。)

```kotlin
init {
    val countStream = buttonTapStream
            .map({ event -> 1 })
            .scan({ sum, n -> sum + n })
            .publish()
            .refCount()
    countStream.subscribe({ n -> count = n.toString() })

    val fizzStream = countStream.map({ n -> n % 3 == 0 })
    fizzStream.subscribe({ isShown ->
        fizzVisibility = if (isShown) View.VISIBLE else View.INVISIBLE
    })

    val buzzStream = countStream.map({ n -> n % 5 == 0 })
    buzzStream.subscribe({ isShown ->
        buzzVisibility = if (isShown) View.VISIBLE else View.INVISIBLE
    })
}
```

## [RxProperty](https://github.com/k-kagurazaka/rx-property-android)を利用して、Bindingしてる感を高めたパターン

2の実装が悪いわけではないのですが、自分でsetterに通知の処理を書いたりするのは、コードも長くなってしまいますし、バグのもとにもなるのできるならやりたくありません。
そこでRxPropertyを利用して、そのあたりをうまいこと解決したパターンが下記になります。

個人的にはこの書き方が一番すきです。(テストしやすい、コード短い、Rx感が漂っている)

短いのでソースを全部のせてみます。

```kotlin
<?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="viewModel"
            type="kyanro.com.androidrxfizzbuzzsample.MainActivity.ViewModel"
            />
    </data>

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="kyanro.com.androidrxfizzbuzzsample.MainActivity"
        >

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="24dp"
            android:text="tap me"
            />

        <TextView
            android:id="@+id/count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="32dp"
            android:text="@{viewModel.count.value}"
            />

        <TextView
            android:id="@+id/fizz"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="32dp"
            android:visibility="@{viewModel.fizzVisibility.value}"
            android:text="Fizz!"
            />

        <TextView
            android:id="@+id/buzz"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="32dp"
            android:visibility="@{viewModel.buzzVisibility.value}"
            android:text="Buzz!"
            />


    </LinearLayout>
</layout>
```

```kotlin
class MainActivity : AppCompatActivity() {

    val binding: ActivityMainBinding by lazy {
        DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val viewModel = ViewModel(RxView.clicks(binding.button))
        binding.viewModel = viewModel
    }

    class ViewModel(buttonTapStream: rx.Observable<Void>) {
        val count: ReadOnlyRxProperty<String>

        val fizzVisibility: ReadOnlyRxProperty<Int>
        val buzzVisibility: ReadOnlyRxProperty<Int>

        init {
            val countStream = buttonTapStream
                    .map({ event -> 1 })
                    .scan({ sum, n -> sum + n })
                    .publish()
                    .refCount()
            count = countStream.map(Int::toString)
                    .toRxProperty("0")

            fizzVisibility = countStream
                    .map({ n -> if (n % 3 == 0) View.VISIBLE else View.INVISIBLE })
                    .toRxProperty(View.VISIBLE)

            buzzVisibility = countStream
                    .map({ n -> if (n % 5 == 0) View.VISIBLE else View.INVISIBLE })
                    .toRxProperty(View.VISIBLE)
        }
    }
}
```

![fizzbuzz.gif](https://qiita-image-store.s3.amazonaws.com/0/18789/0d3659ef-bfca-1dae-d1d6-287c3f95147f.gif)


# まとめ
kotlin, DataBinding, RxJava, RxProperty を利用すると

+ fizz buzzが短くかける
+ バグが入り込みにくそうなコードがかける
+ テストしやすそうなコードがかける

実際にテストしやすいのか、というのは今後試していきたいと思います。