Kotlin Coroutines
Kotlin Coroutines
Presented By
Mohamed Galal
About Course
• Duration (12 hours)
- 2 Lectures (6 hours)
- 2 Labs (6 hours)
• Prerequisites
- Kotlin
- Lambda Expressions
• Evaluation Criteria
- 40% on Labs
- 60% on Project
Agenda
Intro to Asynchronous
What are Coroutines?
Concurrency VS Parallelism
Exception Handling
Channels API
Kotlin Flow API
SharedFlow and StateFlow
Intro to
Asynchronous
Intro in Asynchronous
❑ Thread
❑Callbacks
❑ RX
❑ Coroutine
Elias Ahmed
What are
Coroutines
Intro to Coroutines
Co – routine
cooperative routine (function)
The kind of coroutine builder you use dictate how exceptions will
propagate and how you can handle them.
Exception Handling
• When using launch coroutine builder, exception are thrown as
soon as they happen and are propagated up to the parent.
Exceptions are treated as uncaught exceptions, similar to java’s
Thread.UncaughExceptionHandler.
Demo
Exception Handling
Exception Handling
CoroutineExceptionHandler
Demo
Exception Handling
Exception Handling
Multiple Child Coroutine Exceptions
• You may have multiple coroutines with other child coroutines
running under them. What happens if those child coroutines
throw exceptions? This is where all this might become tricky. In this
case, the general rule is “the first exception wins.” If you set a
CoroutineExceptionHandler, it will manage only the first exception
suppressing all the others.
Exception Handling
Multiple Child Coroutine Exceptions
• If a Coroutine doesn’t handle exceptions by itself with a try-catch
clause, the exception will propagate up the exception to its parent,
therefore, be handled by an outer try-catch clause. Instead, the
exception is “propagated up the job hierarchy”.
3) Propagate
up
2) Cancel parent
1) Cancel children
Exception Handling
Demo
Exception Handling
Supervising Coroutines
• Imagine a UI-related CoroutineScope that processes user interactions.
If a child coroutine throws an exception, the UI scope will be
cancelled and the whole UI component will become unresponsive as
a cancelled scope cannot start more coroutines.
• What if you don’t want that behavior? Alternatively, you can use a
different implementation of Job, namely SupervisorJob, in the
CoroutineContext of the CoroutineScope that creates these
coroutines.
Exception Handling
Supervising Coroutines
• With a supervisorJob the failure of a child doesn’t affect other children.
• The supervisorJob won’t cancel itself or the rest of its children.
• The supervisorJob won’t propagate the exception either, and will let the
child coroutine handle it.
Exception Handling
Demo
Channels
Channels
• Channels are conceptually similar to reactive streams.
• It is a simple abstraction that you can use to transfer a stream of
values between coroutines
Producer Consumer
Channels
• A fundamental property and an important concept to understand
of a channel is its capacity, which defines the maximum number of
elements that a channel can contain in a buffer.
Channels
• Creating a channel is pretty straightforward. Write the following:
val channel = Channel<Int>()
• Producing its values can be done via the usual for loop:
for (x in array) { channel.send(x) }
• Consuming its values can be done via the usual for loop:
for (x in channel) { println(x) }
Channels
• SendChannel exposes the operation close, which is used for
closing the channel. As soon as the sender calls close() on the
channel, the value of isClosedForSend becomes true.
• LiveData is a simple observable data holder. It’s best used to store UI state, such as
lists of items. It’s easy to learn and work with. But it doesn’t provide much more than
that.
• RxJava is a very powerful tool for reactive streams. It has many features and a
plethora of transformation operators. But it has a steep learning curve!
• Flow falls somewhere in between LiveData and RxJava. It’s very powerful but also very
easy to use! The Flow API even looks a lot like RxJava!
Flow builders
• There are the following basic ways to create a flow:
flow{…} function to construct arbitrary flows from sequential calls to emit function.
asFlow() extension functions on various types to convert them into flows.
flowOf(…) functions to create a flow from a fixed set of values.
channelFlow{…} function to construct arbitrary flows from potentially concurrent calls
to the send function.
MutableStateFlow and MutableSharedFlow define the corresponding constructor
functions to create a hot flow that can be directly updated
Intermediate Flow Operators
• After a flow is created, we can call various flow operators to process or convert
values in the flow, and the execution order is sequential.
• Flow provides many different operators, We will introduce some commonly used
operators.
map(): uses to transform each value in the flow, and return a new value to the new
flow operator.
filter(): filters values in flow. Only when predicate returns true, it puts the value into
the new flow.
Intermediate Flow Operators - cont’d
transform(): is a flexible function that may transform emitted element, skip it or
emit it multiple times, This operator generalizes filter and map operators.
take(): is a size-limiting cancel the execution of the flow when the corresponding
limit is reached.
zip(): is a flow operator that emits a single item after combining the emission of
two flow collections via a specified function.
merge(): merges the given flows into a single flow without preserving an order
of elements. All flows are merged concurrently.
buffer(): is a flow emissions via channel of a specified capacity and runs collector
in a separate coroutine.
Terminal Flow Operators
• Because Flows are cold, they won't produce values until a terminal operator is called.
• Terminal operators are suspending functions that start the collection of the flow.
• The collect operator is the most basic one, but there are other terminal operators,
which can make it easier:
Conversion to various collections like toList and toSet.
Operators to get the first value and to ensure that a flow emits a single value.
Reducing a flow to a value with reduce and fold.
Let’s write first Flow
Flow Demo
Switching the Context
• Another thing you can do with Flow events is Switch the context in which you will
consume them.
• To do that, you have to call flowOn(context: CoroutineContext)
• This operator is composable and affects only preceding operators that do not have its
own context.
• The real power of applying context switching is that you can do it as many times as you
want for each operator you’re calling on the flow
• This operator is context preserving.
Switching the Context
Demo
Switching the Context Demo
Flow Constraints
• Because Flow is easy to use, there have to be some constraints to keep people from
abusing or breaking the API. There are two main things each flow should adhere to,
they are:
Demo
Preserving the Context Demo
Exception Transparency
• It’s relatively easy to bury exceptions in coroutines. For example, by using async, you
could effectively receive an exception, but if you never call await, you won’t throw it
for coroutines to catch.
• When emit or emitAll throws, the Flow implementations must immediately stop
emitting new values and finish with an exception. If there is a need to emit values after
the downstream failed, please use the catch operator.
• You can use catch, providing a lambda which will catch any exception you produce in
the stream and any of its previous operators.
Exception Transparency – cont’d
• The emitter can use a catch operator that preserves this exception transparency and
allows encapsulation of its exception handling. The body of the catch operator can
analyze an exception and react to it in different ways depending on which exception
was caught:
• Create a flow that emits twenty even numbers, every second an emission, and
collect the first ten numbers. (Don’t use if-statement or filter operator )