この記事は、スタートトゥデイ工務店 Advent Calendar 15日目の記事です。
はじめに
最近RxとかDataBindingが流行ってきている空気をひしひしと感じるので、このビッグウェーブに乗り遅れないためにfizz buzzします。
動作仕様
- ボタンをタップした回数を表示する
- ボタンがタップされた回数が3で割り切れるなら fizz のViewを表示する。割り切れないなら非表示にする
- ボタンがタップされた回数が5で割り切れるなら buzz のViewを表示する。割り切れないなら非表示にする
下記の3パターンでやってみました。
- 今までの延長上にRxを取り入れるパターン
- ストリームの分岐をつかって、Rxっぽくしてみるパターン
- 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)
}
}
}
まとめ
kotlin, DataBinding, RxJava, RxProperty を利用すると
- fizz buzzが短くかける
- バグが入り込みにくそうなコードがかける
- テストしやすそうなコードがかける
実際にテストしやすいのか、というのは今後試していきたいと思います。