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 package androidx.lifecycle
17 
18 import androidx.annotation.RestrictTo
19 import androidx.annotation.VisibleForTesting
20 import androidx.annotation.WorkerThread
21 import androidx.arch.core.executor.ArchTaskExecutor
22 import java.util.concurrent.Executor
23 import java.util.concurrent.atomic.AtomicBoolean
24 
25 /**
26  * A LiveData class that can be invalidated & computed when there are active observers.
27  *
28  * It can be invalidated via [invalidate], which will result in a call to [compute] if there are
29  * active observers (or when they start observing)
30  *
31  * This is an internal class for now, might be public if we see the necessity.
32  *
33  * @param <T> The type of the live data
34  */
35 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
36 public abstract class ComputableLiveData<T>
37 @JvmOverloads
38 /**
39  * Creates a computable live data that computes values on the specified executor or the arch IO
40  * thread executor by default.
41  *
42  * @param executor Executor that is used to compute new LiveData values.
43  */
44 constructor(internal val executor: Executor = ArchTaskExecutor.getIOThreadExecutor()) {
45 
46     private val _liveData: LiveData<T?> =
47         object : LiveData<T?>() {
onActivenull48             override fun onActive() {
49                 executor.execute(refreshRunnable)
50             }
51         }
52     /** The LiveData managed by this class. */
53     public open val liveData: LiveData<T?> = _liveData
54     internal val invalid = AtomicBoolean(true)
55     internal val computing = AtomicBoolean(false)
56 
57     @JvmField
58     @VisibleForTesting
<lambda>null59     internal val refreshRunnable = Runnable {
60         var computed: Boolean
61         do {
62             computed = false
63             // compute can happen only in 1 thread but no reason to lock others.
64             if (computing.compareAndSet(false, true)) {
65                 // as long as it is invalid, keep computing.
66                 try {
67                     var value: T? = null
68                     while (invalid.compareAndSet(true, false)) {
69                         computed = true
70                         value = compute()
71                     }
72                     if (computed) {
73                         liveData.postValue(value)
74                     }
75                 } finally {
76                     // release compute lock
77                     computing.set(false)
78                 }
79             }
80             // check invalid after releasing compute lock to avoid the following scenario.
81             // Thread A runs compute()
82             // Thread A checks invalid, it is false
83             // Main thread sets invalid to true
84             // Thread B runs, fails to acquire compute lock and skips
85             // Thread A releases compute lock
86             // We've left invalid in set state. The check below recovers.
87         } while (computed && invalid.get())
88     }
89 
90     // invalidation check always happens on the main thread
91     @JvmField
92     @VisibleForTesting
<lambda>null93     internal val invalidationRunnable = Runnable {
94         val isActive = liveData.hasActiveObservers()
95         if (invalid.compareAndSet(false, true)) {
96             if (isActive) {
97                 executor.execute(refreshRunnable)
98             }
99         }
100     }
101 
102     /**
103      * Invalidates the LiveData.
104      *
105      * When there are active observers, this will trigger a call to [.compute].
106      */
invalidatenull107     public open fun invalidate() {
108         ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable)
109     }
110 
111     // TODO https://issuetracker.google.com/issues/112197238
computenull112     @WorkerThread protected abstract fun compute(): T
113 }
114