Skip to content

Instantly share code, notes, and snippets.

@objcode
Created October 14, 2020 22:25
Show Gist options
  • Save objcode/29f11163a365874bd62ba3697f399389 to your computer and use it in GitHub Desktop.
Save objcode/29f11163a365874bd62ba3697f399389 to your computer and use it in GitHub Desktop.
package com.example.myapplication
import android.annotation.SuppressLint
import androidx.annotation.MainThread
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.internal.SafeIterableMap
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Lifecycle.State.DESTROYED
import androidx.lifecycle.Lifecycle.State.STARTED
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
private sealed class PendingData<out T> {
object NotSet : PendingData<Nothing>()
class Ref<T>(val value: T) : PendingData<T>()
}
abstract class LiveData<T> {
/* synthetic access */ val mDataLock = Any()
@SuppressLint("RestrictedApi")
private val mObservers: SafeIterableMap<Observer<in T>, ObserverWrapper> = SafeIterableMap()
// how many observers are in active state
var mActiveCount/* synthetic access */ = 0
// to handle active/inactive reentry, we guard with this boolean
private var mChangingActiveState = false
@Volatile
private var mData: T
// when setData is called, we set the pending data and actual data swap happens on the main
// thread
/* synthetic access */@Volatile
private var mPendingDataRef: PendingData<T> = PendingData.NotSet
val mPendingData: T?
get() = when (val pendingData = mPendingDataRef) {
is PendingData.NotSet -> null
is PendingData.Ref -> pendingData.value
}
var version: Int
private set
private var mDispatchingValue = false
private var mDispatchInvalidated = false
private val mPostValueRunnable: Runnable = Runnable {
var newValue: T
synchronized(mDataLock) {
newValue = mPendingData ?: return@Runnable
mPendingDataRef = PendingData.NotSet
}
setValue(newValue as T)
}
/**
* Creates a LiveData initialized with the given `value`.
*
* @param value initial value
*/
constructor(value: T) {
mData = value
version = START_VERSION + 1
}
/* Edit: This constructor must be removed for null-safety */
/**
* Creates a LiveData with no value assigned to it.
*/
// constructor() {
// mData = PendingData.NotSet
// version = START_VERSION
// }
private fun considerNotify(observer: ObserverWrapper) {
if (!observer.mActive) {
return
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false)
return
}
if (observer.mLastVersion >= version) {
return
}
observer.mLastVersion = version
observer.mObserver?.onChanged(mData)
}
@SuppressLint("RestrictedApi")
fun /* synthetic access */dispatchingValue(initiator: ObserverWrapper?) {
var initiator = initiator
if (mDispatchingValue) {
mDispatchInvalidated = true
return
}
mDispatchingValue = true
do {
mDispatchInvalidated = false
if (initiator != null) {
considerNotify(initiator)
initiator = null
} else {
val iterator: Iterator<Map.Entry<Observer<in T>, ObserverWrapper>> =
mObservers.iteratorWithAdditions()
while (iterator.hasNext()) {
considerNotify(iterator.next().value)
if (mDispatchInvalidated) {
break
}
}
}
} while (mDispatchInvalidated)
mDispatchingValue = false
}
/**
* Adds the given observer to the observers list within the lifespan of the given
* owner. The events are dispatched on the main thread. If LiveData already has data
* set, it will be delivered to the observer.
*
*
* The observer will only receive events if the owner is in [Lifecycle.State.STARTED]
* or [Lifecycle.State.RESUMED] state (active).
*
*
* If the owner moves to the [Lifecycle.State.DESTROYED] state, the observer will
* automatically be removed.
*
*
* When data changes while the `owner` is not active, it will not receive any updates.
* If it becomes active again, it will receive the last available data automatically.
*
*
* LiveData keeps a strong reference to the observer and the owner as long as the
* given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
* the observer &amp; the owner.
*
*
* If the given owner is already in [Lifecycle.State.DESTROYED] state, LiveData
* ignores the call.
*
*
* If the given owner, observer tuple is already in the list, the call is ignored.
* If the observer is already in the list with another owner, LiveData throws an
* [IllegalArgumentException].
*
* @param owner The LifecycleOwner which controls the observer
* @param observer The observer that will receive the events
*/
@SuppressLint("RestrictedApi")
@MainThread
fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
assertMainThread("observe")
if (owner.getLifecycle().getCurrentState() === DESTROYED) {
// ignore
return
}
val wrapper = LifecycleBoundObserver(owner, observer)
val existing: ObserverWrapper? = mObservers.putIfAbsent(observer, wrapper)
if (existing != null && !existing.isAttachedTo(owner)) {
throw IllegalArgumentException(
"Cannot add the same observer"
+ " with different lifecycles"
)
}
if (existing != null) {
return
}
owner.getLifecycle().addObserver(wrapper)
}
/**
* Adds the given observer to the observers list. This call is similar to
* [LiveData.observe] with a LifecycleOwner, which
* is always active. This means that the given observer will receive all events and will never
* be automatically removed. You should manually call [.removeObserver] to stop
* observing this LiveData.
* While LiveData has one of such observers, it will be considered
* as active.
*
*
* If the observer was already added with an owner to this LiveData, LiveData throws an
* [IllegalArgumentException].
*
* @param observer The observer that will receive the events
*/
@SuppressLint("RestrictedApi")
@MainThread
fun observeForever(observer: Observer<in T>) {
assertMainThread("observeForever")
val wrapper = AlwaysActiveObserver(observer)
val existing: ObserverWrapper? = mObservers.putIfAbsent(observer, wrapper)
if (existing is LifecycleBoundObserver) {
throw IllegalArgumentException(
("Cannot add the same observer"
+ " with different lifecycles")
)
}
if (existing != null) {
return
}
wrapper.activeStateChanged(true)
}
/**
* Removes the given observer from the observers list.
*
* @param observer The Observer to receive events.
*/
@SuppressLint("RestrictedApi")
@MainThread
fun removeObserver(observer: Observer<in T>) {
assertMainThread("removeObserver")
val removed: ObserverWrapper = mObservers.remove(observer) ?: return
removed.detachObserver()
removed.activeStateChanged(false)
}
/**
* Removes all observers that are tied to the given [LifecycleOwner].
*
* @param owner The `LifecycleOwner` scope for the observers to be removed.
*/
@MainThread
fun removeObservers(owner: LifecycleOwner) {
assertMainThread("removeObservers")
for (entry: Map.Entry<Observer<in T>, ObserverWrapper> in mObservers) {
if (entry.value.isAttachedTo(owner)) {
removeObserver(entry.key)
}
}
}
/**
* Posts a task to a main thread to set the given value. So if you have a following code
* executed in the main thread:
* <pre class="prettyprint">
* liveData.postValue("a");
* liveData.setValue("b");
</pre> *
* The value "b" would be set at first and later the main thread would override it with
* the value "a".
*
*
* If you called this method multiple times before a main thread executed a posted task, only
* the last value would be dispatched.
*
* @param value The new value
*/
@SuppressLint("RestrictedApi")
protected fun postValue(value: T) {
var postTask: Boolean
synchronized(mDataLock) {
postTask = mPendingData === PendingData.NotSet
mPendingDataRef = PendingData.Ref(value)
}
if (!postTask) {
return
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable)
}
/**
* Sets the value. If there are active observers, the value will be dispatched to them.
*
*
* This method must be called from the main thread. If you need set a value from a background
* thread, you can use [.postValue]
*
* @param value The new value
*/
@MainThread
protected fun setValue(value: T) {
assertMainThread("setValue")
version++
mData = value
dispatchingValue(null)
}
/**
* Returns the current value.
* Note that calling this method on a background thread does not guarantee that the latest
* value set will be received.
*
* @return the current value
*/
@get:Nullable
val value: T?
get() {
val data = mData
return if (data !== NOT_SET) {
data as T
} else null
}
/**
* Called when the number of active observers change from 0 to 1.
*
*
* This callback can be used to know that this LiveData is being used thus should be kept
* up to date.
*/
protected fun onActive() {}
/**
* Called when the number of active observers change from 1 to 0.
*
*
* This does not mean that there are no observers left, there may still be observers but their
* lifecycle states aren't [Lifecycle.State.STARTED] or [Lifecycle.State.RESUMED]
* (like an Activity in the back stack).
*
*
* You can check if there are observers via [.hasObservers].
*/
protected fun onInactive() {}
/**
* Returns true if this LiveData has observers.
*
* @return true if this LiveData has observers
*/
@SuppressLint("RestrictedApi")
fun hasObservers(): Boolean {
return mObservers.size() > 0
}
/**
* Returns true if this LiveData has active observers.
*
* @return true if this LiveData has active observers
*/
fun hasActiveObservers(): Boolean {
return mActiveCount > 0
}
@MainThread
fun changeActiveCounter(change: Int) {
var previousActiveCount = mActiveCount
mActiveCount += change
if (mChangingActiveState) {
return
}
mChangingActiveState = true
try {
while (previousActiveCount != mActiveCount) {
val needToCallActive = previousActiveCount == 0 && mActiveCount > 0
val needToCallInactive = previousActiveCount > 0 && mActiveCount == 0
previousActiveCount = mActiveCount
if (needToCallActive) {
onActive()
} else if (needToCallInactive) {
onInactive()
}
}
} finally {
mChangingActiveState = false
}
}
internal inner class LifecycleBoundObserver(
@NonNull owner: LifecycleOwner,
observer: Observer<in T>?
) : ObserverWrapper(observer), LifecycleEventObserver {
@NonNull
val mOwner: LifecycleOwner = owner
override fun shouldBeActive(): Boolean {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED)
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
var currentState: Lifecycle.State = mOwner.getLifecycle().getCurrentState()
if (currentState === DESTROYED) {
if (mObserver != null) {
removeObserver(mObserver)
}
return
}
var prevState: Lifecycle.State? = null
while (prevState !== currentState) {
prevState = currentState
activeStateChanged(shouldBeActive())
currentState = mOwner.getLifecycle().getCurrentState()
}
}
}
abstract inner class ObserverWrapper internal constructor(observer: Observer<in T>?) {
val mObserver: Observer<in T>?
var mActive = false
var mLastVersion = START_VERSION
abstract fun shouldBeActive(): Boolean
fun isAttachedTo(owner: LifecycleOwner?): Boolean {
return false
}
fun detachObserver() {}
fun activeStateChanged(newActive: Boolean) {
if (newActive == mActive) {
return
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive
changeActiveCounter(if (mActive) 1 else -1)
if (mActive) {
dispatchingValue(this)
}
}
init {
mObserver = observer
}
}
private inner class AlwaysActiveObserver internal constructor(observer: Observer<in T>?) :
ObserverWrapper(observer) {
override fun shouldBeActive(): Boolean {
return true
}
}
companion object {
val START_VERSION = -1
val NOT_SET = Any()
@SuppressLint("RestrictedApi")
fun assertMainThread(methodName: String) {
if (!ArchTaskExecutor.getInstance().isMainThread()) {
throw IllegalStateException(
("Cannot invoke " + methodName + " on a background"
+ " thread")
)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment