Experiments Never Fail

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

published at tags: Android RxJava