Android
Kotlin
RxJava
DataBinding
RxProperty

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

More than 1 year has passed since last update.

この記事は、スタートトゥデイ工務店 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が短くかける

  • バグが入り込みにくそうなコードがかける

  • テストしやすそうなコードがかける

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