RxJava の Observable を Android DataBinding の ObservableField に変換する

 Android DataBinding で View とバインドできるデータクラスは BaseObservable から派生したクラスか、ObservableField<T> 型のフィールドのみです。

 RxJavaベースの API やモデルクラスを使用している場合、更新通知は rx.Observable<T>subscribe することで受けられるわけですが、それを View にバインドするには、ObservableField<T> に変換してあげなければなりません。

 結果、下のような Utility 関数を作ることになります。

/**
 * rx.Observable から ObservableField への変換をおこなう
 */
static public <T> ObservableField<T> toObservableField(Observable<T> source, CompositeSubscription subscriptions) {
    final ObservableField<T> field = new ObservableField<T>();

    subscriptions.add(
            // TODO onError も拾ったほうがいい
            source.subscribe(new Action1<T>() {
                @Override
                public void call(T x) {
                    field.set(x);
                }
            })
    );

    return field;
}

 しかしこの方法はスマートでないと感じます。  どうせ ObservableField も同じような概念のオブジェクトで、View が購読開始-終了をしているにすぎないはずなので、同じタイミングで、rx.Observable<T> の subscribe/unsubscribe をさせてあげれば良いはずです。

 ということで作ってみたのがこの rx.Observable<T>ObservableField<T> に変換するクラス。

import android.databinding.ObservableField;

import java.util.HashMap;
import java.util.Map;

import rx.Observable;
import rx.Subscription;
import rx.functions.Action1;

public class RxField<T> extends ObservableField<T> {

    private final Observable<T> observable;
    private final Map<Integer, Subscription> sucscriptionMap = new HashMap<Integer, Subscription>();

    public RxField(Observable<T> observable) {
        super();
        this.observable = observable;
    }

    public RxField(Observable<T> observable, T defaultValue) {
        super(defaultValue);
        this.observable = observable;
    }

    @Override
    public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        super.addOnPropertyChangedCallback(callback);

        sucscriptionMap.put(callback.hashCode(), observable.subscribe(new Action1<T>() {
            @Override
            public void call(T value) {
                set(value);
            }
        }));
    }

    @Override
    public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        if (sucscriptionMap.containsKey(callback.hashCode())) {
            final Subscription subscription = sucscriptionMap.get(callback.hashCode());
            subscription.unsubscribe();
            sucscriptionMap.remove(callback.hashCode());
        }

        super.removeOnPropertyChangedCallback(callback);
    }

    @Override
    public void set(T value) {
        // TODO should be readonly, because cannot set value to observable
        super.set(value);
    }

    public Observable<T> tObservable() {
        return observable;
    }
}

 ObservableField は、View から購読されると addOnPropertyChangedCallback が呼ばれ、購読解除されると removeOnPropertyChangedCallback が呼ばれます(るはずです)。

 なので、このタイミングで rx.Observable<T>subscribe()subscription.unsubscribe() してあげます。購読者(View)が複数になる可能性があるので、 subscription は Map で管理しています。

 で、rx.Observable<T> の値が変わった時(onNext())に、ObservableFieldset(value) を呼んであげれば、ObservableField 側の変更通知(notifyChanged)が飛んで、View が更新されます。

 使い方はこんな感じで → StopWatchSample/StopWatchAppAndroid/app/src/main/java/com/amay077/stopwatchapp/viewmodel/MainViewModel.java  

双方向には対応してません

 この実装は、rx.Observable の更新を ObservableField 通知するだけです。逆方向(ObservableField の変更を rx.Observable に適用する)は対応していません。そもそも rx.Observable は値を設定できないので、それをしたければ rx.Observable の代わりに rx.Subject が必要です。

Android Data Binding + MVVMパターンのサンプルを書いてみた で作成したアプリに、これを適用してみたので、ご参考まで。