9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LiveDataのpostValueはすべてのデータが通知されるわけではないということを理解して使っていますか?

Last updated at Posted at 2020-11-03

言いたいことはタイトルの通りです。LiveDataのpostValueを使った場合、一部データが通知されない可能性があります。その挙動を理解して使っていれば問題ありませんが、意識しないで使っていると問題が起こるかもというお話です。

要約すると、observerのonChanged()がコールされるまでの間にキューがあるわけではないので、読み出される前に次のデータがpostされると、最後の値しか通知されません。データをUIに反映させるという目的であれば不必要な更新を発生させないので、むしろ好都合というか、そういう意図で作られているのだとは思います。しかし、LiveDataをそれ以外の目的で利用しているなどで、途中のデータも通知されないと困るという用途の場合は注意が必要です。

特に意識していなかった場合は、途中のデータが失われると困る用途で使っていないか再度確認しましょう。
問題がある場合は、LiveDataではなく、RxJavaなど他のライブラリを使うのが良いでしょう。

理由詳細

postValueの実装を覗いてみましょう

LiveData.java

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()
    }
}

以上です。

9
3
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
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?