rxJavaで変化する値の配線をいい感じに扱いたい。


概要

androidJavaを試してみているのですが、Reactっぽく記述するにはrxjavaがちょっと使いにくかった、ので独自ラップクラスで誤魔化した、という話です。
もっと良い方法があったら教えてください。

今回のサンプル

+1, -1ボタンと数字を表示するだけの簡単なものです。
普通にrxJava使うとたぶんこんな感じ。こう書くのが嫌な理由は後述します。
(以降のサンプルコードはkotlinで書いています)

SampleActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val linearlayout2 = LinearLayout(this)
    linearlayout2.setOrientation(LinearLayout.VERTICAL)
    setContentView(linearlayout2)

    val countSubject:PublishSubject<Int> = PublishSubject.create()
    val addButton = Button(this)
    addButton.setText("add 1")
    linearlayout2.addView(addButton)
    addButton.setOnClickListener{v -> countSubject.onNext(1)}

    val minusButton = Button(this)
    minusButton.setText("minus 1")
    linearlayout2.addView(minusButton)
    minusButton.setOnClickListener{v -> countSubject.onNext(-1)}

    val scoreView = TextView(this)
    linearlayout2.addView(scoreView)
    countSubject.scan{ a, b -> a + b }.subscribe{i:Int -> scoreView.setText(i.toString() + "点だよ")}

    countSubject.onNext(0)
}

何が嫌なのか

この例では、ボタンを押すたびに1や-1が流れてくるストリームに対して、Labelがsubscribeして、scanで得られた合計値を使う形ですよね。
でも、これを理解するにはストリームやscanあたりの原理を知らないといけませんよね。無駄にややこしいんですよ。

ストリームという概念は非同期やイベントの統一という視点は面白いのですが、個人的にはそんなものないほうがシンプルで理解しやすいと思っています。

例えばReactではどうしているかというと、扱うのはあくまでただのデータで、その値が変化したらそれに合うようにReactが画面描画する、という仕組みです。非同期処理やイベントは操作したいデータの値を更新するだけで、あとはReactにお任せです。
ストリームなんて概念を知らなくても簡単に書けるんですよ。

目指すゴール

今回の例だと、現在のcountをほぼプリミティブな値として使いたいです。
コードで書くとこんなイメージ

IdealActivity.kt
    val count = いい感じにするクラス<Int>()

    ~~
    addButton.setOnClickListener{Unit ->
        count.setvalue(count.getvalue() + 1)
    }

    ~~
    minusButton.setOnClickListener{Unit ->
        count.setvalue(count.getvalue() - 1)
    }

    ~~
    count.subscribe{v -> scoreView.setText(v.getvalue().toString() + "点だよ")}
    count.setvalue(0)

rxJavaで見つからなかったので無理やり作った

しかし、ざっと探してもそんな例はでてこない。ObservableにもPublishSubjectにもそれっぽいメソッドが見つからない。。

仕方ないので簡単にラップしたクラスを作ってみました。(繰り返しますが、既に似たようなものがあるなら教えていただけると嬉しいです。)

ObservableProperty.kt
import rx.Observable
import rx.Observer
import rx.Subscription
import rx.subjects.PublishSubject


class ObservableProperty<T>(t:T) {

    private var pub:PublishSubject<T>
    private var lastVal: T

    init {
        pub = PublishSubject.create()
        lastVal = t
    }

    fun setValue(t:T){
        lastVal = t
        pub.onNext(t)
    }

    fun getValue():T{
        return lastVal
    }

    fun sub(): Observable<T> {
        return pub
    }

}

使い方はIdealActivityとほぼ同じです。
java(kotlin)での関数リテラルの扱いがいまいちだったので、sub()メソッドが中途半端になってますがあまり問題ないでしょう。

備考

  • 複数の値から一つのViewを作りたい場合、2つのObservablePropertyのsub()を呼び出してmergeすれば使えます。

  • 非同期とか複数イベントとか扱うと無限ループになることがあると思うのですが、そこはfilterとか使えばなんとでもなるでしょう。

  • Androidのマルチスレッド対応はよくわかりませんが、問題ないでしょうきっと。