1 /* 2 * Copyright (C) 2021 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 com.android.car.apps.common.util; 18 19 import static java.util.Objects.requireNonNull; 20 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 import androidx.arch.core.util.Function; 24 import androidx.core.util.Pair; 25 import androidx.lifecycle.LiveData; 26 import androidx.lifecycle.MediatorLiveData; 27 import androidx.lifecycle.MutableLiveData; 28 import androidx.lifecycle.Transformations; 29 30 import java.util.function.BiFunction; 31 import java.util.function.Predicate; 32 33 /** 34 * Utility methods for using {@link LiveData}. In general for Boolean operations, {@code null} is 35 * treated as an "unknown" value, and operations may use short-circuit evaluation to determine the 36 * result. LiveData may be in an uninitialized state where observers are not called when registered 37 * (e.g. a {@link MutableLiveData} where {@link MutableLiveData#setValue(Object)} has not yet been 38 * called). If a Boolean operation receives an uninitialized LiveData as either of its parameters, 39 * the result will also be in an uninitialized state. 40 */ 41 @SuppressWarnings({"unused", "WeakerAccess"}) 42 public class LiveDataFunctions { 43 LiveDataFunctions()44 private LiveDataFunctions() { 45 } 46 47 private static volatile LiveData<?> sNullLiveData; 48 private static volatile LiveData<Boolean> sTrueLiveData; 49 private static volatile LiveData<Boolean> sFalseLiveData; 50 51 /** 52 * Returns a LiveData that always emits {@code null}. This is different than an uninitialized 53 * LiveData in that observers will be called (with {@code null}) when registered. 54 */ nullLiveData()55 public static <T> LiveData<T> nullLiveData() { 56 if (sNullLiveData == null) { 57 sNullLiveData = dataOf(null); 58 } 59 // null can fit any generic type 60 // noinspection unchecked 61 return (LiveData<T>) sNullLiveData; 62 } 63 64 /** Returns a LiveData that is initialized with {@code value}. */ dataOf(@ullable T value)65 public static <T> MutableLiveData<T> dataOf(@Nullable T value) { 66 MutableLiveData<T> data = new MutableLiveData<>(); 67 data.setValue(value); 68 return data; 69 } 70 71 /** 72 * Similar to {@link Transformations#map(LiveData, Function)}, but emits {@code null} when 73 * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable. 74 */ mapNonNull(@onNull LiveData<T> source, @NonNull Function<T, R> func)75 public static <T, R> LiveData<R> mapNonNull(@NonNull LiveData<T> source, 76 @NonNull Function<T, R> func) { 77 return mapNonNull(source, null, func); 78 } 79 80 /** 81 * Similar to {@link Transformations#map(LiveData, Function)}, but emits {@code nullValue} when 82 * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable. 83 */ mapNonNull(@onNull LiveData<T> source, @Nullable R nullValue, @NonNull Function<T, R> func)84 public static <T, R> LiveData<R> mapNonNull(@NonNull LiveData<T> source, @Nullable R nullValue, 85 @NonNull Function<T, R> func) { 86 return Transformations.map(source, value -> value == null ? nullValue : func.apply(value)); 87 } 88 89 /** 90 * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits {@code null} when 91 * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable. 92 */ switchMapNonNull(@onNull LiveData<T> source, @NonNull Function<T, LiveData<R>> func)93 public static <T, R> LiveData<R> switchMapNonNull(@NonNull LiveData<T> source, 94 @NonNull Function<T, LiveData<R>> func) { 95 return switchMapNonNull(source, null, func); 96 } 97 98 /** 99 * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits {@code nullValue} 100 * when {@code source} emits {@code null}. The input to {@code func} may be treated as not 101 * nullable. 102 */ switchMapNonNull(@onNull LiveData<T> source, @Nullable R nullValue, @NonNull Function<T, LiveData<R>> func)103 public static <T, R> LiveData<R> switchMapNonNull(@NonNull LiveData<T> source, 104 @Nullable R nullValue, 105 @NonNull Function<T, LiveData<R>> func) { 106 return Transformations.switchMap(source, 107 value -> value == null ? nullLiveData() : func.apply(value)); 108 } 109 110 /** 111 * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits a FutureData, 112 * which provides a loading field for operations which may take a long time to finish. 113 * 114 * This LiveData emits values only when the loading status of the output changes. It will never 115 * emit {@code null}. If the output is loading, the emitted FutureData will have a null value 116 * for the data. 117 */ loadingSwitchMap(LiveData<T> trigger, @NonNull Function<T, LiveData<R>> func)118 public static <T, R> LiveData<FutureData<R>> loadingSwitchMap(LiveData<T> trigger, 119 @NonNull Function<T, LiveData<R>> func) { 120 LiveData<R> output = Transformations.switchMap(trigger, func); 121 return new MediatorLiveData<FutureData<R>>() { 122 { 123 addSource(trigger, data -> setValue(new FutureData<>(true, null))); 124 addSource(output, data -> 125 setValue(new FutureData<>(false, output.getValue()))); 126 } 127 }; 128 } 129 130 /** 131 * Returns a LiveData backed by {@code value} if and only if predicate emits {@code true}. Emits 132 * {@code null} otherwise. 133 * <p> 134 * This is equivalent to {@code iff(predicate, Boolean::booleanValue, value)} 135 * 136 * @see #iff(LiveData, Predicate, LiveData) 137 */ 138 public static <T> LiveData<T> iff( 139 @NonNull LiveData<Boolean> predicate, @NonNull LiveData<T> value) { 140 return iff(predicate, Boolean::booleanValue, value); 141 } 142 143 /** 144 * Returns a LiveData backed by {@code value} if and only if the trigger emits a value that 145 * causes {@code predicate} to return {@code true}. Emits {@code null} otherwise. 146 */ 147 public static <P, T> LiveData<T> iff( 148 @NonNull LiveData<P> trigger, 149 @NonNull Predicate<? super P> predicate, 150 @NonNull LiveData<T> value) { 151 return new BinaryOperation<>( 152 trigger, value, (p, v) -> p == null || !predicate.test(p) ? null : v); 153 } 154 155 /** 156 * Returns a LiveData that emits a Pair containing the values of the two parameter LiveDatas. If 157 * either parameter is uninitialized, the resulting LiveData is also uninitialized. 158 * <p> 159 * This is equivalent to calling {@code combine(tData, uData, Pair::new)}. 160 * 161 * @see #combine(LiveData, LiveData, BiFunction) 162 */ 163 public static <T, U> LiveData<Pair<T, U>> pair( 164 @NonNull LiveData<T> tData, @NonNull LiveData<U> uData) { 165 return combine(tData, uData, Pair::new); 166 } 167 168 /** 169 * Returns a LiveData that emits the result of {@code function} on the values of the two 170 * parameter LiveDatas. If either parameter is uninitialized, the resulting LiveData is also 171 * uninitialized. 172 */ 173 public static <T, U, R> LiveData<R> combine( 174 @NonNull LiveData<T> tData, 175 @NonNull LiveData<U> uData, 176 @NonNull BiFunction<T, U, R> function) { 177 return new BinaryOperation<>(tData, uData, function); 178 } 179 180 private static class BinaryOperation<T, U, R> extends MediatorLiveData<R> { 181 @NonNull 182 private final BiFunction<T, U, R> mFunction; 183 184 private boolean mTSet; 185 private boolean mUSet; 186 private boolean mValueSet; 187 188 @Nullable 189 private T mTValue; 190 @Nullable 191 private U mUValue; 192 193 BinaryOperation( 194 @NonNull LiveData<T> tLiveData, 195 @NonNull LiveData<U> uLiveData, 196 @NonNull BiFunction<T, U, R> function) { 197 this(tLiveData, uLiveData, true, true, function); 198 } 199 200 BinaryOperation( 201 @NonNull LiveData<T> tLiveData, 202 @NonNull LiveData<U> uLiveData, 203 boolean requireTSet, 204 boolean requireUSet, 205 @NonNull BiFunction<T, U, R> function) { 206 this.mFunction = function; 207 if (!requireTSet) { 208 mTSet = true; 209 } 210 if (!requireUSet) { 211 mUSet = true; 212 } 213 if (tLiveData == uLiveData) { 214 // Only add the source once and only update once when it changes. 215 addSource( 216 tLiveData, 217 value -> { 218 mTSet = true; 219 mUSet = true; 220 mTValue = value; 221 // if both references point to the same LiveData, then T and U are 222 // compatible types. 223 // noinspection unchecked 224 mUValue = (U) value; 225 update(); 226 }); 227 } else { 228 addSource(requireNonNull(tLiveData), this::updateT); 229 addSource(requireNonNull(uLiveData), this::updateU); 230 } 231 } 232 233 private void updateT(@Nullable T tValue) { 234 mTSet = true; 235 this.mTValue = tValue; 236 update(); 237 } 238 239 private void updateU(@Nullable U uValue) { 240 mUSet = true; 241 this.mUValue = uValue; 242 update(); 243 } 244 245 private void update() { 246 if (mTSet && mUSet) { 247 R result = mFunction.apply(mTValue, mUValue); 248 // Don't setValue if it's the same as the old value unless we haven't set a value 249 // before. 250 if (!mValueSet || result != getValue()) { 251 mValueSet = true; 252 setValue(result); 253 } 254 } 255 } 256 } 257 } 258