言いたいことはタイトルの通りです。LiveDataのpostValueを使った場合、一部データが通知されない可能性があります。その挙動を理解して使っていれば問題ありませんが、意識しないで使っていると問題が起こるかもというお話です。
要約すると、observerのonChanged()
がコールされるまでの間にキューがあるわけではないので、読み出される前に次のデータがpostされると、最後の値しか通知されません。データをUIに反映させるという目的であれば不必要な更新を発生させないので、むしろ好都合というか、そういう意図で作られているのだとは思います。しかし、LiveDataをそれ以外の目的で利用しているなどで、途中のデータも通知されないと困るという用途の場合は注意が必要です。
特に意識していなかった場合は、途中のデータが失われると困る用途で使っていないか再度確認しましょう。
問題がある場合は、LiveDataではなく、RxJavaなど他のライブラリを使うのが良いでしょう。
理由詳細
postValueの実装を覗いてみましょう
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
mPostValueRunnableの実装は以下
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
setValue((T) newValue);
}
};
setValue()
を非同期で実行するのだけど、 mPostValueRunnable
が実行されるまでの間に、次の postValue()
が実行された場合は、 mPostValueRunnable
はpostされません。 mPostValueRunnable
が拾う値が更新されているので、最終的な値が通知されることにはなりますが、その前の値は通知されません。
ちなみに、setValue()
の実装は以下です。 dispatchingValue()
の中で Observer#onChanged()
のコールまで実行していますので、 setValue()
の場合は通知されない値はありません。
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
確認
UIスレッドからコールした場合も同じなので、以下のコードで確認してみましょう。
val liveData = MutableLiveData<String>()
liveData.observe(this) {
Log.e("XXXX", "observe: $it")
}
findViewById<View>(R.id.button).setOnClickListener {
liveData.postValue("1")
liveData.postValue("2")
}
observeには 2
のみが通知されて 1
は通知されません。
RxJavaのObservableをLiveDataに変換
RxJavaのObservableをLiveDataとして扱いたい場合は、以下のようにすることになると思います。
LiveDataとして扱う以上、通知が連続した場合に最後の値しか通知されない前提で使うのでこれで良いと思います。
fun <T> Observable<T>.toLiveData(): LiveData<T> = RxLiveData(this)
class RxLiveData<T>(
private val observable: Observable<T>
) : LiveData<T>() {
private var disposable: Disposable? = null
override fun onActive() {
disposable = observable
.subscribe({
postValue(it)
}, {
postValue(null)
})
}
override fun onInactive() {
disposable?.dispose()
}
}
全部通知したい場合は以下のようにするのかなと思いますが、この場合はObservableをそのまま扱った方が良いでしょうね。
class RxLiveData<T>(
private val observable: Observable<T>
) : LiveData<T>() {
private var disposable: Disposable? = null
override fun onActive() {
disposable = observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
setValue(it)
}, {
setValue(null)
})
}
override fun onInactive() {
disposable?.dispose()
}
}
以上です。