/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.lifecycle; import androidx.annotation.CallSuper; import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.arch.core.internal.SafeIterableMap; import java.util.Map; /** * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on * {@code OnChanged} events from them. *

* This class correctly propagates its active/inactive states down to source {@code LiveData} * objects. *

* Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object: * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback * is called for either of them, we set a new value in {@code liveDataMerger}. * *

 * LiveData<Integer> liveData1 = ...;
 * LiveData<Integer> liveData2 = ...;
 *
 * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
 * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
 * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
 * 
*

* Let's consider that we only want 10 values emitted by {@code liveData1}, to be * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code * liveData1} and remove it as a source. *

 * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
 *      private int count = 1;
 *
 *      {@literal @}Override public void onChanged(@Nullable Integer s) {
 *          count++;
 *          liveDataMerger.setValue(s);
 *          if (count > 10) {
 *              liveDataMerger.removeSource(liveData1);
 *          }
 *      }
 * });
 * 
* * @param The type of data hold by this instance */ @SuppressWarnings("WeakerAccess") public class MediatorLiveData extends MutableLiveData { private SafeIterableMap, Source> mSources = new SafeIterableMap<>(); /** * Creates a MediatorLiveData with no value assigned to it. */ public MediatorLiveData() { super(); } /** * Creates a MediatorLiveData initialized with the given {@code value}. * * @param value initial value */ public MediatorLiveData(T value) { super(value); } /** * Starts to listen to the given {@code source} LiveData, {@code onChanged} observer will be * called when {@code source} value was changed. *

* {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active. *

If the given LiveData is already added as a source but with a different Observer, * {@link IllegalArgumentException} will be thrown. * * @param source the {@code LiveData} to listen to * @param onChanged The observer that will receive the events * @param The type of data hold by {@code source} LiveData */ @MainThread public void addSource(@NonNull LiveData source, @NonNull Observer onChanged) { if (source == null) { throw new NullPointerException("source cannot be null"); } Source 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(); } } /** * Stops to listen the given {@code LiveData}. * * @param toRemote {@code LiveData} to stop to listen * @param the type of data hold by {@code source} LiveData */ @MainThread public void removeSource(@NonNull LiveData toRemote) { Source source = mSources.remove(toRemote); if (source != null) { source.unplug(); } } @CallSuper @Override protected void onActive() { for (Map.Entry, Source> source : mSources) { source.getValue().plug(); } } @CallSuper @Override protected void onInactive() { for (Map.Entry, Source> source : mSources) { source.getValue().unplug(); } } private static class Source implements Observer { final LiveData mLiveData; final Observer mObserver; int mVersion = START_VERSION; Source(LiveData liveData, final Observer 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); } } } }