136436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar/*
236436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * Copyright (C) 2017 The Android Open Source Project
336436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar *
436436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * you may not use this file except in compliance with the License.
636436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * You may obtain a copy of the License at
736436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar *
836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
936436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar *
1036436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * Unless required by applicable law or agreed to in writing, software
1136436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
1236436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1336436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * See the License for the specific language governing permissions and
1436436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * limitations under the License.
1536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar */
1636436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
17ba069d50913c3fb250bb60ec310439db36895337Alan Viverettepackage androidx.lifecycle;
1836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
19ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.annotation.MainThread;
20ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.annotation.NonNull;
21ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.annotation.RestrictTo;
22ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.annotation.VisibleForTesting;
23ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.annotation.WorkerThread;
24ddee2b5170ae257a7b2494f8aaa8459ebed806dcAurimas Liutikasimport androidx.arch.core.executor.ArchTaskExecutor;
2536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
26f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craikimport java.util.concurrent.Executor;
2736436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyarimport java.util.concurrent.atomic.AtomicBoolean;
2836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
2936436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar/**
30f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik * A LiveData class that can be invalidated & computed when there are active observers.
31f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik * <p>
32f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik * It can be invalidated via {@link #invalidate()}, which will result in a call to
33f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik * {@link #compute()} if there are active observers (or when they start observing)
3436436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * <p>
3536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * This is an internal class for now, might be public if we see the necessity.
3636436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar *
3736436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * @param <T> The type of the live data
3836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar * @hide internal
3936436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar */
4036436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
41f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyarpublic abstract class ComputableLiveData<T> {
42f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar
43f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik    private final Executor mExecutor;
44f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar    private final LiveData<T> mLiveData;
4536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
4636436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    private AtomicBoolean mInvalid = new AtomicBoolean(true);
4745adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar    private AtomicBoolean mComputing = new AtomicBoolean(false);
4836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
4936436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    /**
50f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik     * Creates a computable live data that computes values on the arch IO thread executor.
5136436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar     */
52459caadc8f6875fc78a36ae716193bf991f0808cSergey Vasilinets    @SuppressWarnings("WeakerAccess")
5336436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    public ComputableLiveData() {
54f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik        this(ArchTaskExecutor.getIOThreadExecutor());
55f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik    }
56f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik
57f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik    /**
58f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik     *
59f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik     * Creates a computable live data that computes values on the specified executor.
60f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik     *
61f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik     * @param executor Executor that is used to compute new LiveData values.
62f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik     */
63f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik    @SuppressWarnings("WeakerAccess")
64f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik    public ComputableLiveData(@NonNull Executor executor) {
65f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik        mExecutor = executor;
66f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar        mLiveData = new LiveData<T>() {
67f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar            @Override
68f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar            protected void onActive() {
69f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik                mExecutor.execute(mRefreshRunnable);
70f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar            }
71f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar        };
7236436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    }
7336436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
74f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar    /**
75f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar     * Returns the LiveData managed by this class.
76f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar     *
77f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar     * @return A LiveData that is controlled by ComputableLiveData.
78f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar     */
79f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar    @SuppressWarnings("WeakerAccess")
80f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar    @NonNull
81f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar    public LiveData<T> getLiveData() {
82f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar        return mLiveData;
8336436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    }
8436436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
8536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    @VisibleForTesting
8636436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    final Runnable mRefreshRunnable = new Runnable() {
8736436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar        @WorkerThread
8836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar        @Override
8936436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar        public void run() {
9045adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar            boolean computed;
9145adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar            do {
9245adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                computed = false;
9345adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                // compute can happen only in 1 thread but no reason to lock others.
9445adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                if (mComputing.compareAndSet(false, true)) {
9545adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                    // as long as it is invalid, keep computing.
9645adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                    try {
9745adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                        T value = null;
9845adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                        while (mInvalid.compareAndSet(true, false)) {
9945adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                            computed = true;
10045adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                            value = compute();
10145adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                        }
10245adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                        if (computed) {
103f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar                            mLiveData.postValue(value);
10445adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                        }
10545adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                    } finally {
10645adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                        // release compute lock
10745adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                        mComputing.set(false);
10845adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                    }
10945adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                }
11045adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                // check invalid after releasing compute lock to avoid the following scenario.
11145adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                // Thread A runs compute()
11245adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                // Thread A checks invalid, it is false
11345adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                // Main thread sets invalid to true
11445adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                // Thread B runs, fails to acquire compute lock and skips
11545adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                // Thread A releases compute lock
11645adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar                // We've left invalid in set state. The check below recovers.
11745adc615a9243a77b99a172bc4410dadc91e24b1Yigit Boyar            } while (computed && mInvalid.get());
11836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar        }
11936436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    };
12036436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
12136436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    // invalidation check always happens on the main thread
12236436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    @VisibleForTesting
12336436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    final Runnable mInvalidationRunnable = new Runnable() {
12436436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar        @MainThread
12536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar        @Override
12636436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar        public void run() {
127f59164365fb6de9f148b597af5a6e19b3b7c8c2eYigit Boyar            boolean isActive = mLiveData.hasActiveObservers();
12836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar            if (mInvalid.compareAndSet(false, true)) {
12936436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar                if (isActive) {
130f9c95209c71f8513fc5cc71406c53b89dfa39cc0Chris Craik                    mExecutor.execute(mRefreshRunnable);
13136436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar                }
13236436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar            }
13336436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar        }
13436436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    };
13536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
13636436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    /**
13736436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar     * Invalidates the LiveData.
13836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar     * <p>
13936436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar     * When there are active observers, this will trigger a call to {@link #compute()}.
14036436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar     */
14136436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    public void invalidate() {
142ae36c8b11a64d3cdc9ba6e37d9f3d1d250fdc4a8Yigit Boyar        ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
14336436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    }
14436436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar
14536436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    @SuppressWarnings("WeakerAccess")
14636436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    @WorkerThread
14736436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar    protected abstract T compute();
14836436741fe52fa90bbeeddf7baa05f97d734f5f1Yigit Boyar}
149