LoginSignup
28
29

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-02-26

 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パターンのサンプルを書いてみた で作成したアプリに、これを適用してみたので、ご参考まで。

28
29
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
28
29