/* * Copyright (C) 2017 volders GmbH with <3 in Berlin * * 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 berlin.volders.rxdiff; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView.Adapter; import java.lang.ref.WeakReference; import rx.Completable; import rx.Observable; import rx.functions.Func1; /** * {@code RxDiffUtil} calculates and applies the diff between new and old data. * The {@code RxDiffUtil} instance should be applied to an observable with the * {@link Observable#to(Func1)} method. After chaining all actions, this also * transforms the {@link Observable} into a shared {@link Completable}. * <pre> * service.observeData() * .compose(transformer) * .onBackpressureLatest() * .to(RxDiffUtil.with(adapter)) * .calculateDiff(callback) * .applyDiff(adapter::setUnsafe) * .subscribe(); * </pre> * * @param <T> type of the data set * @param <A> type of the adapter */ public class RxDiffUtil<A extends Adapter, T> { final WeakReference<A> adapter; final Observable<T> o; RxDiffUtil(WeakReference<A> adapter, Observable<T> o) { this.adapter = adapter; this.o = o; } /** * @param cb callback to provide the {@link DiffUtil.Callback} to calculate the diff * @return an {@link RxDiffResult} to apply to the adapter */ public RxDiffResult<A, T> calculateDiff(@NonNull Callback<A, T> cb) { return calculateDiff(cb, true); } /** * @param cb callback to provide the {@link DiffUtil.Callback} to calculate the diff * @param dm should try to detect moved items * @return an {@link RxDiffResult} to apply to the adapter */ public RxDiffResult<A, T> calculateDiff(@NonNull Callback<A, T> cb, boolean dm) { return new RxDiffResult<>(o.lift(new OnCalculateDiff<>(adapter, cb, dm))); } /** * @param o old data to use to calculate the diff * @param cb callback to provide the {@link DiffUtil.Callback} to calculate the diff * @return an {@link RxDiffResult} to apply to the adapter */ public RxDiffResult<A, T> calculateDiff(@NonNull Func1<? super A, ? extends T> o, @NonNull Callback2<T> cb) { return calculateDiff(o, cb, true); } /** * @param o old data to use to calculate the diff * @param cb callback to provide the {@link DiffUtil.Callback} to calculate the diff * @param dm should try to detect moved items * @return an {@link RxDiffResult} to apply to the adapter */ public RxDiffResult<A, T> calculateDiff(@NonNull Func1<? super A, ? extends T> o, @NonNull Callback2<T> cb, boolean dm) { return calculateDiff(new RxDiffUtilCallback1<>(o, cb), dm); } /** * @param adapter the adapter to apply the diff to * @param <T> type of the data set * @param <A> type of the adapter * @return a transformer function to use with {@link Observable#to(Func1)} */ public static <A extends Adapter, T> Func1<Observable<T>, RxDiffUtil<A, T>> with(final A adapter) { return new ToRxDiffUtil<>(adapter); } /** * A callback to create a {@link DiffUtil.Callback} from new data only to calculate the diff. * The callback has to keep track of the current data set of the adapter itself. * * @param <T> type of the data set */ public interface Callback<A extends Adapter, T> { /** * @param newData data to update the adapter with * @return a {@link DiffUtil.Callback} for {@code newData} */ @NonNull DiffUtil.Callback diffUtilCallback(@NonNull A adapter, @Nullable T newData); } /** * A callback to create a {@link DiffUtil.Callback} from current and new data * to calculate the diff. * * @param <T> type of the data set */ public interface Callback2<T> { /** * @param oldData data currently displayed in the adapter * @param newData data to update the adapter with * @return a {@link DiffUtil.Callback} for {@code oldData} and {@code newData} */ @NonNull DiffUtil.Callback diffUtilCallback(@Nullable T oldData, @Nullable T newData); } /** * Exception thrown if the {@link Observable} modified with {@code RxDiffUtil} * is still active after the adapter was cleared. */ public static class SubscriptionLeak extends IllegalStateException { SubscriptionLeak() { } } }