When I read blogs or learn knowledge, what I learn is scattered and there is no independent knowledge module concept, and it is easy to forget after learning. So I set up my own notes warehouse (a note warehouse I maintain for a long time, if you are interested, you can click a star~ your star is a great motivation for me to write), and I classified all the things I learned at ordinary times and put them in it, which is also convenient for review when I need.

Kotlin’s coroutine encapsulates the threading API, which makes it easy to write asynchronous code.

While coroutines are convenient enough, they can be even more convenient when used in conjunction with the KTX extension for Architecture components provided by Google.

1. Add a KTX dependency

// Use Kotlin coroutines with architectural components

//ViewModelScope
implementation 'androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.3.1'
//LifecycleScope
implementation 'androidx. Lifecycle: lifecycle - runtime - KTX: 2.2.0'
//liveData
implementation 'androidx. Lifecycle: lifecycle - livedata - KTX: 2.2.0'
Copy the code

2. viewModelScope

2.1 Use coroutines in ViewModel the old way

Before we use ViewModelScope, let’s review the way we used coroutines in viewModels. Manage the CoroutineScope yourself and clear it when it is not needed (typically at onCleared()). Otherwise, resources may be wasted and memory may be leaked.

class JetpackCoroutineViewModel : ViewModel() {
    // When using coroutines in the ViewModel, you need to use this job to facilitate control cancellation
    private val viewModelJob = SupervisorJob()
    
    // Specify where the coroutine is executed, and the viewModelJob can easily cancel the uiScope
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
    
    fun launchDataByOldWay(a) {
        uiScope.launch {
            // Execute in the background
            val result = getNetData()
            / / modify the UI
            log(result)
        }
    }
    
    override fun onCleared(a) {
        super.onCleared()
        viewModelJob.cancel()
    }
    
    // Cut time-consuming tasks to the IO thread for execution
    private suspend fun getNetData(a) = withContext(Dispatchers.IO) {
        // Simulate network time
        delay(1000)
        // The simulation returns the result
        "{}"}}Copy the code

There seems to be a lot of boilerplate code, and it’s easy to forget to fetch coroutines when you don’t need them.

2.2 New ways to use coroutines in viewModels

It is in this context that Google created the ViewModelScope, which makes it easy to use coroutines by adding extended properties to the ViewModel class and automatically cancels its subcoroutines when the ViewModel is destroyed.

class JetpackCoroutineViewModel : ViewModel() {
    fun launchData(a) {
        viewModelScope.launch {
            // Execute in the background
            val result = getNetData()
            / / modify the UI
            log(result)
        }
    }

    // Cut time-consuming tasks to the IO thread for execution
    private suspend fun getNetData(a) = withContext(Dispatchers.IO) {
        // Simulate network time
        delay(1000)
        // The simulation returns the result
        "{}"}}Copy the code

All of the CoroutineScope initialization and cancellation is done for us, just use the viewModelScope inside the code to start a new coroutine without worrying about forgetting to cancel.

Let’s take a look at how Google does it.

2.3 The low-level implementation of viewModelScope

Point to see the source code, know the root, in case of what strange bug encountered behind, in the case of knowing the principle, to think of a solution faster.

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"

/** * [CoroutineScope] tied to this [ViewModel]. * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called * * This scope is bound to * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] */
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        // Return the value from the cache
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if(scope ! =null) {
            return scope
        }
        // Create a CloseableCoroutineScope with no cache
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close(a) {
        coroutineContext.cancel()
    }
}
Copy the code

ViewModelScope is an extended property of ViewModel. Its actual type is CloseableCoroutineScope. The name looks like a cancelable coroutine, and sure enough, it implements Closeable and cancelable in the close method.

Each time a viewModelScope is used, it is fetched from the cache first, and if not, a New CloseableCoroutineScope is created. Note that the execution of CloseableCoroutineScope is performed in the main thread.

What we need to know now is how the cache is stored and retrieved.

//ViewModel.java

// Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
/** * Returns the tag associated with this viewmodel and the specified key. */
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T getTag(String key) {
    if (mBagOfTags == null) {
        return null;
    }
    synchronized (mBagOfTags) {
        return(T) mBagOfTags.get(key); }}/**
 * Sets a tag associated with this viewmodel and a key.
 * If the given {@code newValue} is {@link Closeable},
 * it will be closed once {@link #clear()}.
 * <p>
 * If a value was already set for the given key, this calls do nothing and
 * returns currently associated value, the given {@code newValue} would be ignored
 * <p>
 * If the ViewModel was already cleared then close() would be called on the returned object if
 * it implements {@link Closeable}. The same object may receive multiple close calls, so method
 * should be idempotent.
 */
@SuppressWarnings("unchecked")
<T> T setTagIfAbsent(String key, T newValue) {
    T previous;
    synchronized (mBagOfTags) {
        previous = (T) mBagOfTags.get(key);
        if (previous == null) {
            mBagOfTags.put(key, newValue);
        }
    }
    T result = previous == null ? newValue : previous;
    if (mCleared) {
        // It is possible that we'll call close() multiple times on the same object, but
        // Closeable interface requires close method to be idempotent:
        // "if the stream is already closed then invoking this method has no effect." (c)
        closeWithRuntimeException(result);
    }
    return result;
}

Copy the code

Now we know that it’s in mBagOfTags in the ViewModel, which is a HashMap.

Knowing how to store it, when was it used?

@MainThread
final void clear(a) {
    mCleared = true;
    // Since clear() is final, this method is still called on mock objects
    // and in those cases, mBagOfTags is null. It'll always be empty though
    // because setTagIfAbsent and getTag are not final so we can skip
    // clearing it
    if(mBagOfTags ! =null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
                closeWithRuntimeException(value);
            }
        }
    }
    onCleared();
}

private static void closeWithRuntimeException(Object obj) {
    if (obj instanceof Closeable) {
        try {
            ((Closeable) obj).close();
        } catch (IOException e) {
            throw newRuntimeException(e); }}}Copy the code

I searched for mBagOfTags in the ViewModel and found a clear method. In this method, I iterate over mBagOfTags and close all Closeable values. In the above source code, when viewModelScope is used for the first time, a CloseableCoroutineScope is created, which implements the Closeable interface and the close method, just to cancel.

This tells us that the coroutine built by viewModelScope is fetched when the ViewModel’s clear method is called back.

The clear method also contains the familiar onCleared method call. OnCleared we know what it does, and it will call back when the ViewModel is no longer in use. We usually need to do some finishing work in this method, such as unsubscribe the observer, close the resource, etc.

So, as a wild guess, the clear() method should also be called when the ViewModel is about to end its life.

The clear method is called from the ViewModelStore.

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if(oldViewModel ! =null) { oldViewModel.onCleared(); }}final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys(a) {
        return new HashSet<>(mMap.keySet());
    }

    /** * Clears internal storage and notifies ViewModels that they are no longer used. */
    public final void clear(a) {
        for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code

The ViewModelStore is a container for holding the ViewModel. The clear methods of all viewModels in the ViewModelStore are called in the ViewModelStore clear method. So where is ViewModelStore clear called? I followed up and found that it was in the constructor for ComponentActivity.

public ComponentActivity(a) {
    Lifecycle lifecycle = getLifecycle();
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if(! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); }Copy the code

Call ViewModelStore Clear to finish the Activity life cycle when it reaches onDestroy. Note, however, that this call is conditional on onDestroy not calling clear because of a configuration change.

ViewModelScope’s coroutines cancel automatically (ViewModel’s mBagOfTags) and when (ViewModel’s clear()).

3. lifecycleScope

For Lifecycle, Google has nicely provided LifecycleScope, and we can create a Coroutine directly through launch.

3.1 the use of

A simple example would be updating the text of a TextView every 100 milliseconds in an Activity’s onCreate.

lifecycleScope.launch {
    repeat(100000) {
        delay(100)
        tvText.text = "$it"}}Copy the code

LifeCycle is aware of the LifeCycle of a component, so once an Activity onDestroy, the corresponding lifecycleScope is described above. The call to the launch closure is also cancelled.

In addition, lifecycleScope helpfully provides launchWhenCreated, launchWhenStarted, and launchWhenResumed methods, which have coroutine scopes in their closures, They are executed when CREATED, STARTED, and RESUMED.

1 / / way
lifecycleScope.launchWhenStarted {
    repeat(100000) {
        delay(100)
        tvText.text = "$it"}}2 / / way
lifecycleScope.launch {
    whenStarted { 
        repeat(100000) {
            delay(100)
            tvText.text = "$it"}}}Copy the code

The same effect can be achieved by calling the launchWhenStarted directly or in launch.

3.2 Low-level implementation of LifecycleScope

Let’s take a look at lifecycleScope. Launch

/** * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle]. * * This scope will be cancelled when the [Lifecycle] is destroyed. * * This scope is bound to * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]. */
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope
Copy the code

Boy, extended properties again. This extension is LifecycleOwner, which returns a LifecycleCoroutineScope. Every time a get is returned lifecycle. CoroutineScope, look what that is.

/** * [CoroutineScope] tied to this [Lifecycle]. * * This scope will be cancelled when the [Lifecycle] is destroyed. * *  This scope is bound to * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] */
val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = mInternalScopeRef.get(a)as LifecycleCoroutineScopeImpl?
            if(existing ! =null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }
Copy the code

Lifecycle’s coroutineScope is also an extension property, which is a LifecycleCoroutineScope. As you can see from the comments, the coroutine is cancelled after Lifecycle is destroyed. Here first before from mInternalScopeRef deposited in the cache, without generating a LifecycleCoroutineScopeImpl put in, and call the LifecycleCoroutineScopeImpl register function. MInternalScopeRef = new AtomicReference<>(); Lifecycle mInternalScopeRef = new AtomicReference<>(); (AtomicReference allows an object to be atomic.) AtomicReference is used here, of course, for thread-safety reasons.

Since the generated is LifecycleCoroutineScopeImpl, then let’s look at what is this thing

internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    init {
        // in case we are initialized on a non-main thread, make a best effort check before
        // we return the scope. This is not sync but if developer is launching on a non-main
        // dispatcher, they cannot be 100% sure anyways.
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            coroutineContext.cancel()
        }
    }

    fun register(a) {
        Lifecycle state is greater than or equal to INITIALIZED, so register an observer that will Lifecycle
        launch(Dispatchers.Main.immediate) {
            if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
                lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)}else {
                coroutineContext.cancel()
            }
        }
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        Remove the observer and remove the coroutine when you see that the current lifecycle is less than or equal to DESTROYED
        if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
            lifecycle.removeObserver(this)
            coroutineContext.cancel()
        }
    }
}
Copy the code

In the code above, there are two important functions: Register and onStateChanged. The register function is called when the initialization LifecycleCoroutineScopeImpl, first on the register function to add a observer is used to observe the change of the life cycle, The observer is then removed and the coroutine removed as the lifecycle is checked to DESTROYED in onStateChanged.

One small detail: why can a register function start a coroutine directly? Because LifecycleCoroutineScopeImpl inherited LifecycleCoroutineScope,, And realized LifecycleCoroutineScope CoroutineScope interface (is actually implemented in LifecycleCoroutineScopeImpl).

public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
    internal abstract val lifecycle: Lifecycle
    ......
}
Copy the code

LifecycleScope will build a coroutine when it is used. It will also observe the component’s lifecycle and remove the coroutine when appropriate.

In the example above we saw this code:

1 / / way
lifecycleScope.launchWhenStarted {
    repeat(100000) {
        delay(100)
        tvText.text = "$it"}}2 / / way
lifecycleScope.launch {
    whenStarted { 
        repeat(100000) {
            delay(100)
            tvText.text = "$it"}}}Copy the code

The launchWhenCreated, launchWhenStarted, and launchWhenResumed can be executed at the appropriate lifecycle time directly through the launchWhenCreated, launchWhenStarted, and launchWhenResumed provided by lifecycleScope.

Click in and have a look

abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
    internal abstract val lifecycle: Lifecycle

    /** * Launches and runs the given block when the [Lifecycle] controlling this * [LifecycleCoroutineScope] is at least in  [Lifecycle.State.CREATED] state. * * The returned [Job] will be cancelled when the [Lifecycle] is destroyed. *@see Lifecycle.whenCreated
     * @see Lifecycle.coroutineScope
     */
    fun launchWhenCreated(block: suspend CoroutineScope. () - >Unit): Job = launch {
        lifecycle.whenCreated(block)
    }

    /** * Launches and runs the given block when the [Lifecycle] controlling this * [LifecycleCoroutineScope] is at least in  [Lifecycle.State.STARTED] state. * * The returned [Job] will be cancelled when the [Lifecycle] is destroyed. *@see Lifecycle.whenStarted
     * @see Lifecycle.coroutineScope
     */

    fun launchWhenStarted(block: suspend CoroutineScope. () - >Unit): Job = launch {
        lifecycle.whenStarted(block)
    }

    /** * Launches and runs the given block when the [Lifecycle] controlling this * [LifecycleCoroutineScope] is at least in  [Lifecycle.State.RESUMED] state. * * The returned [Job] will be cancelled when the [Lifecycle] is destroyed. *@see Lifecycle.whenResumed
     * @see Lifecycle.coroutineScope
     */
    fun launchWhenResumed(block: suspend CoroutineScope. () - >Unit): Job = launch {
        lifecycle.whenResumed(block)
    }
}
Copy the code

It turns out that these functions are inside the LifecycleCoroutineScope class returned by the LifecycleOwner extension property lifecycleScope. Lifecycle is called directly without doing anything

/**
 * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.CREATED] state.
 *
 * @see Lifecycle.whenStateAtLeast for details
 */
suspend fun <T> Lifecycle.whenCreated(block: suspend CoroutineScope. () - >T): T {
    return whenStateAtLeast(Lifecycle.State.CREATED, block)
}

/**
 * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.STARTED] state.
 *
 * @see Lifecycle.whenStateAtLeast for details
 */
suspend fun <T> Lifecycle.whenStarted(block: suspend CoroutineScope. () - >T): T {
    return whenStateAtLeast(Lifecycle.State.STARTED, block)
}

/**
 * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.RESUMED] state.
 *
 * @see Lifecycle.whenStateAtLeast for details
 */
suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope. () - >T): T {
    return whenStateAtLeast(Lifecycle.State.RESUMED, block)
}
Copy the code

These functions were originally suspend functions and are functions that extend Lifecycle. They all end up calling the whenStateAtLeast function and passing in the minimal lifecycle state flag (minState) that executes the coroutine.

suspend fun <T> Lifecycle.whenStateAtLeast(
    minState: Lifecycle.State,
    block: suspend CoroutineScope. () - >T
) = withContext(Dispatchers.Main.immediate) {
    valjob = coroutineContext[Job] ? : error("when[State] methods should have a parent job")
    val dispatcher = PausingDispatcher()
    val controller =
        LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
    try {
        // Execute the coroutine
        withContext(dispatcher, block)
    } finally {
        // End work removes lifecycle observations
        controller.finish()
    }
}

@MainThread
internal class LifecycleController(
    private val lifecycle: Lifecycle,
    private val minState: Lifecycle.State,
    private val dispatchQueue: DispatchQueue,
    parentJob: Job
) {
    private val observer = LifecycleEventObserver { source, _ ->
        if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
            //DESTROYED-> remove the DESTROYED process
            handleDestroy(parentJob)
        } else if (source.lifecycle.currentState < minState) {
            dispatchQueue.pause()
        } else {
            / / execution
            dispatchQueue.resume()
        }
    }

    init {
        // If Lifecycle is already destroyed (e.g. developer leaked the lifecycle), we won't get
        // an event callback so we need to check for it before registering
        // see: b/128749497 for details.
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            handleDestroy(parentJob)
        } else {
            // Observe life cycle changes
            lifecycle.addObserver(observer)
        }
    }

    @Suppress("NOTHING_TO_INLINE") // avoid unnecessary method
    private inline fun handleDestroy(parentJob: Job) {
        parentJob.cancel()
        finish()
    }

    /** * Removes the observer and also marks the [DispatchQueue] as finished so that any remaining * runnables can be executed. */
    @MainThread
    fun finish(a) {
        // Remove the lifecycle observer
        lifecycle.removeObserver(observer)
        // Mark completion and execute the remaining executable Runnable
        dispatchQueue.finish()
    }
}

Copy the code

WhenStateAtLeast is also an extension function for Lifecycle. The core logic is to add LifecycleObserver to LifecycleController to listen for Lifecycle state and use that state to decide whether to suspend, resume, or cancel execution. When the execution is complete, finally, finish from the finish executing the LifecycleController: Remove the lifecycle listener and start executing the remaining tasks.

Once completed, the lifecycle observer is removed, which is equivalent to the closure that we wrote to a function like launchWhenResumed being executed only once. After the execution is complete, the onPause->onResume will not be executed again.

4. liveData

In our daily use of LiveData, we may be involved in this scenario: go to the network to get the results, then transfer the data through LiveData, receive notification in the Activity, and then update the UI. Very common scenario, in which case we can simplify the above scenario code by using the official liveData constructor function.

4.1 the use of

val netData: LiveData<String> = liveData {
    // Execute immediately when observed within the lifecycle
    val data = getNetData()
    emit(data)}// Cut time-consuming tasks to the IO thread for execution
private suspend fun getNetData(a) = withContext(Dispatchers.IO) {
    // Simulate network time
    delay(5000)
    // The simulation returns the result
    "{}"
}

Copy the code

In the example above, getNetData() is a suspend function. Use the LiveData constructor function to call getNetData() asynchronously and then use the emit() to submit the result. If you observe the netData on the Activity side and it is active, you will receive the result. As we know, the suspend function needs to be called in coroutine scope, so the liveData closure also has coroutine scope.

One small detail is that if the component happens to be active at the time it observes this netData, the code inside the liveData closure executes immediately.

In addition to the above usage, you can emit multiple values in liveData.

val netData2: LiveData<String> = liveData {
    delay(3000)
    val source = MutableLiveData<String>().apply {
        value = "11111"
    }
    val disposableHandle = emitSource(source)

    delay(3000)
    disposableHandle.dispose()
    val source2 = MutableLiveData<String>().apply {
        value = "22222"
    }
    val disposableHandle2 = emitSource(source2)
}
Copy the code

It should be noted that when calling emitSource, dispose function should be called to cut off the return value of the previous emitSource.

4.2 Low-level implementation of liveData

Old rules, Ctrl+ left mouse click to see the source code

@UseExperimental(ExperimentalTypeInference::class)
fun <T> liveData(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT,
    @BuilderInference block: suspend LiveDataScope<T>. () - >Unit
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)

// The code we wrote in the closure behind liveData is passed to this block, which is a suspend function in the context of LiveDataScope
Copy the code

First of all, what catches your eye is that the liveData function is a global function, which means you can use it anywhere, not just in the Activity or ViewModel.

Second, the liveData function returns a CoroutineLiveData object, right? I’m returning an object, and I’m not doing any code here. So where does my code get executed?

This depends on the code of the CoroutineLiveData class

internal class CoroutineLiveData<T>(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT,
    block: Block<T>
) : MediatorLiveData<T>() {
    private var blockRunner: BlockRunner<T>?
    private var emittedSource: EmittedSource? = null

    init {
        // use an intermediate supervisor job so that if we cancel individual block runs due to losing
        // observers, it won't cancel the given context as we only cancel w/ the intention of possibly
        // relaunching using the same parent context.
        val supervisorJob = SupervisorJob(context[Job])

        // The scope for this LiveData where we launch every block Job.
        // We default to Main dispatcher but developer can override it.
        // The supervisor job is added last to isolate block runs.
        val scope = CoroutineScope(Dispatchers.Main.immediate + context + supervisorJob)
        blockRunner = BlockRunner(
            liveData = this,
            block = block,
            timeoutInMs = timeoutInMs,
            scope = scope
        ) {
            blockRunner = null}}internal suspend fun emitSource(source: LiveData<T>): DisposableHandle {
        clearSource()
        val newSource = addDisposableSource(source)
        emittedSource = newSource
        return newSource
    }

    internal suspend fun clearSource(a){ emittedSource? .disposeNow() emittedSource =null
    }

    override fun onActive(a) {
        super.onActive() blockRunner? .maybeRun() }override fun onInactive(a) {
        super.onInactive() blockRunner? .cancel() } }Copy the code

There is less code in it, mainly inheriting MediatorLiveData, and then executing BlockRunner’s maybeRun function when onActive. BlockRunner’s maybeRun execution is actually the code block we wrote in liveData, and the onActive method is actually inherited from liveData and is called when there is an active observer listening on liveData.

This makes sense. In my example above, I observed netData in the Activity’s onCreate (active state), so the code in liveData is executed immediately.


//typealias typealias
// This is used in BlockRunner below, which is used to host our code in the closure behind liveData
internal typealias Block<T> = suspend LiveDataScope<T>.() -> Unit

/** * Handles running a block at most once to completion. */
internal class BlockRunner<T>(
    private val liveData: CoroutineLiveData<T>,
    private val block: Block<T>,
    private val timeoutInMs: Long.private val scope: CoroutineScope,
    private val onDone: () -> Unit
) {
    @MainThread
    fun maybeRun(a){.../ / the scope here is CoroutineScope (Dispatchers. Main. Immediate + + supervisorJob context)
        runningJob = scope.launch {
            val liveDataScope = LiveDataScopeImpl(liveData, coroutineContext)
            // This block executes the code we wrote in liveData, passing in the liveDataScope instance when executing the block, and the liveDataScope context is available
            block(liveDataScope)
            / / finish
            onDone()
        }
    }
    ...
}

internal class LiveDataScopeImpl<T>(
    internal var target: CoroutineLiveData<T>,
    context: CoroutineContext
) : LiveDataScope<T> {

    ...
    // use `liveData` provided context + main dispatcher to communicate with the target
    // LiveData. This gives us main thread safety as well as cancellation cooperation
    private val coroutineContext = context + Dispatchers.Main.immediate
    
    // Since there is a LiveDataScopeImpl context when executing the liveData closure, you can use the EMIT function
    override suspend fun emit(value: T) = withContext(coroutineContext) {
        target.clearSource()
        // Set the value to the target LiveData, which returns the target. If you observe the target in the component, you will receive the value data
        target.value = value
    }
}

Copy the code

BlockRunner’s maybeRun function starts a coroutine. This scope is initialized in CoroutineLiveData: CoroutineScope (Dispatchers. Main. Immediate + + supervisorJob context), then there is within the scope to perform behind liveData closure inside we write the code, And there is a LiveDataScopeImpl context. With the LiveDataScopeImpl context, we can use the EMIT method in LiveDataScopeImpl. The emit method is as simple as passing data to a LiveData object. This LiveData is the one returned by LiveData {}. At this point, because the LiveData data has changed, if a component has observed the LiveData and the component is active, the component will receive a callback that the data has changed.

The general process is to build a coroutine in liveData{} and return a liveData. Then the code inside the closure we write is actually executed in a coroutine. When we call the emit method, we update the value in the liveData. Since it is LiveData returned, it is naturally associated with the component lifecycle, and results are only available when the component is active, and some memory leaks are avoided.

5. Summary

I have to say, the official offer is convenience, which makes it a lot easier for us to use coroutines. Partners who are using coroutines and are not already using them with architectural components do so. A fun ~

The resources

  • Developer.android.com/kotlin/coro…
  • Developer.android.com/topic/libra…