Kotlin Coroutines: Fundamentals

Feb 14 2024 · Kotlin 1.9, Android 13, Android Studio Giraffe

Part 2: Deep Dive into Coroutines

07. Understand Coroutine Scope & Structured Concurrency

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 06. Use Coroutine Builders: launch, async, runBlocking Next episode: 08. Explore Coroutine Jobs

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 07. Understand Coroutine Scope & Structured Concurrency

Hi! In this episode you’ll learn about coroutine scopes and the structured concurrency in Kotlin Coroutines.

The CoroutineScope defines a world in which the coroutines live.

Usually, the scope is a way to attach the coroutine to some form of a lifecycle. For example, a lifecycle of an Android activity.

In most of the cases you can cancel the scope, all the coroutines attached to it will be cancelled as well.

Without any scope, you could easily create memory leaks, or coroutines which never end.

The coroutines from the same scope can affect each other. For example cancellation of one coroutine can cancel the others.

Under the hood the scope is just a wrapper around the CoroutineContext.

It exposes handy extension functions like coroutine builders you learned about in the previous episode and the cancel function.

The common building blocks of the Android app are the Activities, Fragments, ViewModels and Composables.

Each of them has a lifecycle. The coroutines started from them should also be bound to those lifecycles.

Let’s say the user clicked on some button starting a network request and then left the screen before that request finished.

In most of the cases you would like to cancel the request because its result is not needed anymore.

The coroutine performing the request should be cancelled when the activity dies.

The Jetpack library provides the scopes for each of the components.

In the activities and fragments you can use the lifecycleScope properties.

The viewModelScope is available in the ViewModels.

The composables have the rememberCoroutineScope() and LaunchedEffect composable functions.

Kotlin Coroutines follows a principle of structured concurrency. There are several rules to follow to achieve that.

Firstly, all the coroutines are bound to some scope. You can’t start a coroutine without a scope.

launch and async builders are extensions functions on the CoroutineScope. runBlocking is a top-level function which provide the scope.

If you start another coroutine from within a coroutine, they will form a parent-child hierarchy.

If you cancel the parent, it will also cancel all the children. Also, the new children won’t start from a cancelled parent.

If one of the child throws an uncaught exception, the effect depends on the Job of the parent scope.

In case of regular Job, the failure will cancel the parent and its other children and grandchildren.

If the parent uses a SupervisorJob, a failure won’t affect the parent and the other siblings of the failed coroutine.

Let’s see how it works. Open the ScopesScreen file and look at the second button click handler.

At the beginning there is coroutine context, similar to one you may recall from the previous episode.

It is here also to simplify the sample app usage. Without it the app may crash in some scenarios. Now it will print exceptions to the LogCat instead.

In the scope associated with that context we create a nested scope which contains two children.

One child completes successfully, and the other one fails.

Now, check how it behaves.

Build the app and go to the Scopes sceen.

Click the second button and look at the LogCat.

After two seconds the failure message from the exception handler appeared in logs. The message about the end of second child and the parent are not there.

It means that both the second child and the parent were cancelled before they reached those lines.

Let’s check what happens if you explicitly cancel the parent. Add the following line at the end of the first onClick handler:

errorHandlingScope.cancel()

Run the app and navigate to the ScopesScreen.

click the second button and immediately click the first button.

Observe the logs.

This time there are no messages about the first and second children as well as the parent. It means that they all were cancelled.

Finally, replace the coroutineScope builder in the startNestedCoroutines function with the supervisorScope.

Run the app.

Go the Scopes screen.

Click the second button.

Open the LogCat.

This time the second child and a parent reached their ends despite the failure of the first child.

A supervisorScope prevented the failure of the first child from cancelling the parent and the second child.

That’s it for this episode. I hope you got the idea of structured concurrency.

In the next episode you’ll learn more about the coroutine Jobs.