Is providing all the informations needed to execute a Coroutine • Is keeping track of all the children Coroutines executed with the scope • Is providing methods to control the entire lifecycle of all the Coroutines ◦ (eg. launch, cancel and join methods) 27
thread / thread pool where we want to execute our Coroutine. Some example of Dispatchers: • Dispatchers.Main (Main thread in Android) • Dispatchers.Default (Thread pool optimized for background compute intensive operations) • Dispatchers.IO (Thread pool optimized for IO operations) 31
containing elements from this context and elements from other [context]. 3. * The elements from this context with the same key as in the other one are dropped. 4. */ 5. public operator fun plus(context: CoroutineContext): CoroutineContext 36
enough to use one of the already existing Scopes provided from the Android KTX Libraries. Some examples: • viewModelScope (for ViewModels) • lifecycleScope (for Activities and Fragments) • viewLifecycleOwner.lifecycleScope (for Fragments to refers to the view lifecycle) 41
Lifecycle already handled ( eg the scope is cancelled ) • No need to handle the scope creation • Specific extension for dealing with Android Lifecycle ( eg launchWhenStarted) 42
If you want to provide more details on the cancellation reason you can provide an instance of CancellationException when calling .cancel() Under the hood, the child job notifies its parent about the cancellation via the exception. The parent uses the cause of the cancellation to determine whether it needs to handle the exception. If the child was cancelled due to CancellationException, then no other action is required for the parent. 58
behavior. Exceptions that occur will be caught and returned to a callback function handleException(...), also as we cannot recover from the exception inside of it, because the coroutine had already completed with the corresponding exception when the handler is called. 60
catches all its exceptions and represents them in the resulting Deferred object when you call await(), so it cannot result in uncaught exceptions. 1. val rootScope = CoroutineScope(Job()) 2. val deferred = rootScope.async { 3. codeThatCanThrowExceptions() 4. } 5. try { 6. deferred.await() 7. } catch (exception: Exception) { 8. println(“ Handled $exception") } 9. } 62
coroutine it will go up to the parent even if we wrap it with a try/catch block. 1. val scope = CoroutineScope(Job()) 2. scope.launch { 3. async { 4. // If async throws, launch throws without calling .await() 5. } 6. } 63
more producer coroutines write to a channel. One or more consumer coroutines can read from the same channel. A channel has a suspending send function and a suspending receive function. This means that several coroutines can use channels to pass data to each other in a non-blocking fashion. 69
basically represents the Channel’s sender. With an openSubscription to return a channel to subscribe to. Note: It is important to understand here that only when the subscription is obtained will the consumer receive the values being sent into the channel. Anything sent before obtaining the subscription is not received by the subscribed consumers of the channel. 74
to consume items sent in the channel but it differs in one aspect that a ConflatedBroadcastChannel only emits the most recently sent item while the older items are lost. Also, any future subscribers to this channel will receive the item that was most recently emitted same as the Conflated Channel. Note: ConflatedBroadcastChannels is deprecated in favor of StateFlow since kotlin 1.4.0 and became stable as StateFlow offers more capilites “as a Flow API member”, simpler to use and makes more sense for state management. 75
use ConflatedBroadcastChannel instead of BehaviorSubject , and BroadcastChannel instead of PublishSubject as they both do the same job as their RxJava counterparts. 76
that efficient to use, also not built to be used as a reactive streams and have a margin of errors due to closing the channel. We Needed an abstraction for cold streams that was lazy and computed data in a “push” mode. Hence the Flow API was created. 78
from a fixed set of values. asFlow() extension functions on various types to convert them into flows like collections or liveData. flow { … } builder function to construct arbitrary flows from sequential calls to emit function. callbackFlow { … } use channel to emit values. send(), offer(), awaitClose{} 83
into flows. • send: used inside coroutines, suspends the producer until there's space for the new element • offer: does not add the element to the channel and returns false immediately Synchronously. • awaitClose: the callback inside awaitClose will be executed when the flow is either closed or cancelled. 84
you would with collections and sequences. Intermediate operators are applied to an upstream flow and return a downstream flow. These operators are cold, just like flows are. A call to such an operator is not a suspending function itself. It works quickly, returning the definition of a new transformed flow. Official documentation map catch debounce distinctUntilChanged onEach buffer 86
Will create a flow of 3 items 2. .map { it + 1 } // Increase each emission by 1 3. .flowOn(ctxA) 4. .filter{ it == 3} 5. 6. withContext(Dispatchers.Main) { 7. val result = testFlow.single() // Getting the result as a return value 8. print(result) 9. 10.} 89
2. .map { it + 1 } 3. .flowOn(ctxA) // Changes the upstream context 4. .filter{ it == 3} // Just emitting the item if it’s 3 5. 6. withContext(Dispatchers.Main) { // Running the flow in a Main Scope 7. // All non-encapsulated operators will be executed in Main: filter and single 8. val result = testFlow.single() 9. print(result) 10.} 90
{ computeOne(it) } 3. .catch { ... } 4. .map { computeTwo(it) } // Maps the emissions using the computeTwo() 5. .collect { process(it) } // Collect the emissions from the flow and process() them 6. // Any exceptions from process() and computeTwo() will not be catched 92
for live data updates and endless streams of data using the asFlow() extension. • You can use flow with Room to be notified of changes in the database. • You can collect the flow seamlessly in a way that will handle its lifetime with viewModelScope, lifeCycleScope or using the launchIn() operator and passing the desired scope. 93
third party, as flow is feature of Kotlin language. • Over livedata: • It is a violation to the clean architecture to use livedata inside the Data layer, as it is a part of the platform. • Cannot collect data from livedata outside view, as livedata needs a lifecycle. 95
fit your needs with functions like: replayCache(), resetReplayCache() • Can set the number of most recent values to emit with constructor param (replay = Number) • Works well with Events ex: User clicks a button and emitting a clickEvent StateFlow • Cache single most recent value • Can retrieve cached values replayCache() • Cannot reset cache • Must Create StateFlow with initial value • Works well with States ex: fetching a data from a Data Source and updating screen state 98
as you just need to include the org.jetbrains.kotlinx:kotlinx-coroutines-test:$versions.coroutinesVersion, and use the runBlockingTest() function to launch your code inside an Immediate execution dispatcher & a TestCoroutineScope. Dispatchers.setMain will override the Main dispatcher in test situations. This is helpful when you want to execute a test in situations where the platform Main dispatcher is not available. And you can also inject a TestCoroutineDispatcher to your class and use it instead of your normal dispatchers to force a sequential execution. 102