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

  • 14
    いいね
  • 0
    コメント

この記事は、スタートトゥデイ工務店 Advent Calendar 15日目の記事です。

はじめに

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

動作仕様

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

下記の3パターンでやってみました。
1. 今までの延長上にRxを取り入れるパターン
2. ストリームの分岐をつかって、Rxっぽくしてみるパターン
3. RxPropertyを利用して、Bindingしてる感を高めたパターン

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

実装

今までの延長上にRxを取り入れるパターン

MainActivity.kt
activity_main.xml

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

  • 一つのストリームだけを利用して、subscribe時にタップ数から表示、非表示の判定をしている部分
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
activity_main.xml

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

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を利用して、Bindingしてる感を高めたパターン

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

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

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

<?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>
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

まとめ

kotlin, DataBinding, RxJava, RxProperty を利用すると

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

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

この投稿は スタートトゥデイ工務店 Advent Calendar 201615日目の記事です。