このテストは通る
class SampleViewModel : ViewModel() {
private val _liveData = MutableLiveData<Boolean>()
val liveData: LiveData<Boolean>
get() = _liveData
fun updateLiveData(bool: Boolean) {
_liveData.value = bool
}
}
class SampleViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
@Test
fun testUpdateLiveData() {
val viewModel = SampleViewModel()
assertThat(viewModel.liveData.value).isNull()
viewModel.updateLiveData(true)
assertThat(viewModel.liveData.value).isTrue()
}
}
しかし、liveDataをdistinctUntilChanged
を用いるように修正すると、assertThat(viewModel.liveData.value).isTrue()
のところで通らなくなる
class SampleViewModel : ViewModel() {
private val _liveData = MutableLiveData<Boolean>()
val liveData: LiveData<Boolean>
get() = _liveData.distinctUntilChanged() // 修正
fun updateLiveData(bool: Boolean) {
_liveData.value = bool
}
}
原因 1
distinctUntilChanged
の実装を見てみると、MediatorLiveDataを新たに生成し、ソースを追加して、LiveDataとして返している。
つまり、先のコードではliveData
を取得しようとする度に、別のLiveDataが返ってきていた。
また、ソースのLiveData(_liveData
)の値に関わらず、初期値がnull
のMediatorLiveDataが生成されているので、先のテストでは常にviewModel.liveData.value == null
となる
...
@MainThread
@NonNull
public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source) {
final MediatorLiveData<X> outputLiveData = new MediatorLiveData<>();
outputLiveData.addSource(source, new Observer<X>() {
boolean mFirstTime = true;
@Override
public void onChanged(X currentValue) {
final X previousValue = outputLiveData.getValue();
if (mFirstTime
|| (previousValue == null && currentValue != null)
|| (previousValue != null && !previousValue.equals(currentValue))) {
mFirstTime = false;
outputLiveData.setValue(currentValue);
}
}
});
return outputLiveData;
}
...
対応
ゲッターを用いず、フィールドに保持する。
class SampleViewModel : ViewModel() {
private val _liveData = MutableLiveData<Boolean>()
val liveData: LiveData<Boolean> = _liveData.distinctUntilChanged() // 修正
fun updateLiveData(bool: Boolean) {
_liveData.value = bool
}
}
原因 2
addSource
の実装を見てみると、最後にhasActiveObservers
でMediatorLiveDataがアクティブなObserverを持っているかどうかを確認し、持っている場合のみplug
を呼び出している。
...
@MainThread
public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
Source<S> e = new Source<>(source, onChanged);
Source<?> existing = mSources.putIfAbsent(source, e);
if (existing != null && existing.mObserver != onChanged) {
throw new IllegalArgumentException(
"This source was already added with the different observer");
}
if (existing != null) {
return;
}
if (hasActiveObservers()) {
e.plug();
}
}
...
plug
の実装を見てみると、ソースのLiveDataをobserve
している。
つまり、MediatorLiveData(liveData
)が、アクティブなObserverによってobserve
されていない場合、ソースのLiveData(_liveData
)の値の変更を受け取らない実装になっている。
そのため、先のコードではliveData
の値は更新されず、初期値であるnull
のままとなる。
...
private static class Source<V> implements Observer<V> {
final LiveData<V> mLiveData;
final Observer<? super V> mObserver;
int mVersion = START_VERSION;
Source(LiveData<V> liveData, final Observer<? super V> observer) {
mLiveData = liveData;
mObserver = observer;
}
void plug() {
mLiveData.observeForever(this);
}
void unplug() {
mLiveData.removeObserver(this);
}
@Override
public void onChanged(@Nullable V v) {
if (mVersion != mLiveData.getVersion()) {
mVersion = mLiveData.getVersion();
mObserver.onChanged(v);
}
}
}
...
対応
テスト内でliveData
をobserve
する。
class SampleViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
@Test
fun testUpdateLiveData() {
val viewModel = SampleViewModel()
viewModel.liveData.observeForever {} // 追加
assertThat(viewModel.liveData.value).isNull()
viewModel.updateLiveData(true)
assertThat(viewModel.liveData.value).isTrue()
}
}
まとめ
MutableLiveDataを用いる時に、ゲッターを利用することもあると思うが、distinctUntilChanged
を使う場合は、フィールドで保持した方が良い。
また、distinctUntilChanged
やmap
、switchMap
など、MediatorLiveDataを用いる時は、observe
しないと値が更新されない。