1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.lifecycle; 18 19 import androidx.annotation.CallSuper; 20 import androidx.annotation.MainThread; 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 import androidx.arch.core.internal.SafeIterableMap; 24 25 import java.util.Map; 26 27 /** 28 * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on 29 * {@code OnChanged} events from them. 30 * <p> 31 * This class correctly propagates its active/inactive states down to source {@code LiveData} 32 * objects. 33 * <p> 34 * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them 35 * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object: 36 * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for 37 * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback 38 * is called for either of them, we set a new value in {@code liveDataMerger}. 39 * 40 * <pre> 41 * LiveData<Integer> liveData1 = ...; 42 * LiveData<Integer> liveData2 = ...; 43 * 44 * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>(); 45 * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value)); 46 * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value)); 47 * </pre> 48 * <p> 49 * Let's consider that we only want 10 values emitted by {@code liveData1}, to be 50 * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code 51 * liveData1} and remove it as a source. 52 * <pre> 53 * liveDataMerger.addSource(liveData1, new Observer<Integer>() { 54 * private int count = 1; 55 * 56 * {@literal @}Override public void onChanged(@Nullable Integer s) { 57 * count++; 58 * liveDataMerger.setValue(s); 59 * if (count > 10) { 60 * liveDataMerger.removeSource(liveData1); 61 * } 62 * } 63 * }); 64 * </pre> 65 * 66 * @param <T> The type of data hold by this instance 67 */ 68 @SuppressWarnings("WeakerAccess") 69 public class MediatorLiveData<T> extends MutableLiveData<T> { 70 private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>(); 71 72 /** 73 * Starts to listen the given {@code source} LiveData, {@code onChanged} observer will be called 74 * when {@code source} value was changed. 75 * <p> 76 * {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active. 77 * <p> If the given LiveData is already added as a source but with a different Observer, 78 * {@link IllegalArgumentException} will be thrown. 79 * 80 * @param source the {@code LiveData} to listen to 81 * @param onChanged The observer that will receive the events 82 * @param <S> The type of data hold by {@code source} LiveData 83 */ 84 @MainThread addSource(@onNull LiveData<S> source, @NonNull Observer<? super S> onChanged)85 public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) { 86 Source<S> e = new Source<>(source, onChanged); 87 Source<?> existing = mSources.putIfAbsent(source, e); 88 if (existing != null && existing.mObserver != onChanged) { 89 throw new IllegalArgumentException( 90 "This source was already added with the different observer"); 91 } 92 if (existing != null) { 93 return; 94 } 95 if (hasActiveObservers()) { 96 e.plug(); 97 } 98 } 99 100 /** 101 * Stops to listen the given {@code LiveData}. 102 * 103 * @param toRemote {@code LiveData} to stop to listen 104 * @param <S> the type of data hold by {@code source} LiveData 105 */ 106 @MainThread removeSource(@onNull LiveData<S> toRemote)107 public <S> void removeSource(@NonNull LiveData<S> toRemote) { 108 Source<?> source = mSources.remove(toRemote); 109 if (source != null) { 110 source.unplug(); 111 } 112 } 113 114 @CallSuper 115 @Override onActive()116 protected void onActive() { 117 for (Map.Entry<LiveData<?>, Source<?>> source : mSources) { 118 source.getValue().plug(); 119 } 120 } 121 122 @CallSuper 123 @Override onInactive()124 protected void onInactive() { 125 for (Map.Entry<LiveData<?>, Source<?>> source : mSources) { 126 source.getValue().unplug(); 127 } 128 } 129 130 private static class Source<V> implements Observer<V> { 131 final LiveData<V> mLiveData; 132 final Observer<? super V> mObserver; 133 int mVersion = START_VERSION; 134 Source(LiveData<V> liveData, final Observer<? super V> observer)135 Source(LiveData<V> liveData, final Observer<? super V> observer) { 136 mLiveData = liveData; 137 mObserver = observer; 138 } 139 plug()140 void plug() { 141 mLiveData.observeForever(this); 142 } 143 unplug()144 void unplug() { 145 mLiveData.removeObserver(this); 146 } 147 148 @Override onChanged(@ullable V v)149 public void onChanged(@Nullable V v) { 150 if (mVersion != mLiveData.getVersion()) { 151 mVersion = mLiveData.getVersion(); 152 mObserver.onChanged(v); 153 } 154 } 155 } 156 } 157