14
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

スタートトゥデイ工務店Advent Calendar 2016

Day 15

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

Last updated at Posted at 2016-12-15

この記事は、スタートトゥデイ工務店 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が短くかける
  • バグが入り込みにくそうなコードがかける
  • テストしやすそうなコードがかける

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

14
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?