0% found this document useful (0 votes)
40 views

Kotlin Coroutines

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views

Kotlin Coroutines

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 65

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)

A coroutine is a concurrency design pattern that you can use on Android to


simplify code that executes asynchronously.

A coroutine is an instance of suspendable computation. It is conceptually


similar to a thread. However, a coroutine is not bound to any particular thread. It
may suspend its execution in one thread and resume in another one.
Concurrency VS
Parallelism
Concurrency
• Concurrency is when two or more tasks can start, run, and complete in
overlapping time periods.
• It doesn't mean they'll ever both be running at the same instant.
• Concurrency: Interruptability
Parallelism
• Parallelism is when tasks literally run at the same instant.
• Parallelism: Independentability
Why Coroutines?
Coroutines are lightweight and super fast.
Fewer memory leaks.
Built-in cancellation support.
Jetpack integration.
Exception handling
Exception Handling
• Exception handling is an integral part of asynchronous programming

• Imagine that you start an asynchronous operation, it runs through


without any errors and finishes with the result. That’s an ideal case.
What if an error occurred during the exceptions? As with any
unhandled exception. The application would crash.
Exception Handling
Exception Propagation
• Exception handling is rather straightforward in coroutines.
• If the code throughs an exception, the environment will propagate it
without you having to do anything.
• Coroutines make asynchronous code look synchronous. Thus, you can use
the same try/catch block to handle exceptions, like in synchronous code.

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.

• When async is used as a root coroutine builder, exceptions are


thrown only when call await().
Exception Handling

Demo
Exception Handling
Exception Handling
CoroutineExceptionHandler

• You can customize the behavior by creating a custom


CoroutineExceptionHandler, which serves as catch block.

• CoroutineExceptionHandler is an element of a CoroutineContext

• CoroutineExceptionHandler catches only exceptions that were not


handled in other way. i.e. uncaught exceptions.
Exception Handling
CoroutineExceptionHandler

• CoroutineExceptionHandler is invoked only on exceptions which are not


excepted to be handled by the user, so registration it in async coroutines
builder and the like of I has no effort.

• CoroutineExceptionHandler is useful when you want to have a global


exception handler shared between coroutines

Let’s see an example…


Exception Handling
Exception Handling
Try-Catch to rescue
• When it comes to handling exceptions for a specific coroutine, you
can use a try-catch block to catch exception and handle them like you
would do in normal synchronous programming in kotlin.
• Corotines created with async coroutone builder can typically swallow
exceptions if you’re not careful.
• If an exception is thrown during an execution of the async block, the
exception is not thrown immediately. Instead, it will be thrown at the
time you call await() on the returned Deferred object.
Exception Handling

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.

• ReceiveChannel exposes the cancel operation, which cancels the


reception of remaining elements from the channel. Once finished,
this function closes the channel and removes all messages in the
buffer, if any. After cancel() completes, isClosedForReceive starts
returning true.
Demo
More reliable way
Channels
• With channels, you always have a producer and a consumer.
Sometimes, a consumer receives the data from a channel, applies
some transformations and becomes the producer of a new
channel. When a consumer of a channel becomes the producer of
another channel, you create a Pipelines.
Channels
• Fan-out
Multiple coroutines may receive from the same channel,
distributing work between themselves.
Channels
• Fan-in
Multiple coroutines may send to the same channel.

For more info


Check Doc
Kotlin Flow
Kotlin Flow
• Flows is a type that can emit multiple values sequentially, as
opposed to suspend functions that return only a single value. For
example, you can use a flow to receive live updates from a
database.

• Flows are designed to handle the asynchronous stream of data.

• Flows supports are cold asynchronously-built value streams

• Flows are a feature of Coroutines.


Kotlin Flow’s Entities
• There are three entities involved in streams of data:
Kotlin Flow Vs LiveData Vs RxJava
Kotlin Flow Vs LiveData Vs RxJava
• All of them provide a way to implement the observer pattern, So what’s the
difference between them?

• 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:

Preserving the Context.


Exception Transparency.
Preserving the Context
• The flow has a context preservation property: it encapsulates its own execution
context and never propagates or leaks it downstream.
• The producing and consuming contexts have to be the same, This effectively means
you can’t have concurrent value production because the Flow itself is not Thread-safe.
Preserving the Context

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:

Exceptions can be rethrown using throw


Exceptions can be turned into emission of values using emit from the body of catch
Exceptions can be ignored, logged, or processed by some other code.
Any Questions ?
Labs
Labs
• Carry out Channel API and Flow API exercises from the lecture as a proof of
concept.

• 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 )

• Apply the Flow API concept, in the Product project.


Thank you

You might also like