Slide 1

Slide 1 text

Kotlin Coroutines - How to use it right

Slide 2

Slide 2 text

Hello! I am Yoyo/Thuy I am here because I am a woman and love sharing knowledge. 2

Slide 3

Slide 3 text

Problem 1 I want to execute long-running tasks in background thread, then update Views.

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

6 Job Deferred join(): Unit await(): T Start a new coroutine launch async Return type Suspend till finish

Slide 7

Slide 7 text

7 fun main() = runBlocking { val job1 = launch { println("Start Job 1") val job2 = launch { println("Start Job 2") delay(100) launch { println("Start Job 3") delay(200) } println("Job 2 - Done!!") } job2.join() println("Job 1 - Done!!") } println("I am not blocking :)") job1.join() println("Done!!") } I am not blocking :) Start Job 1 Start Job 2 Job 2 - Done!! Start Job 3 Job 1 - Done!! Done!!

Slide 8

Slide 8 text

8 ThreadPool Thread 1 Thread 2 Dispatcher Coroutine 1 Coroutine 3 Coroutine 2 waits for Coroutine 2 Coroutine 1 starts Coroutine 2 starts Coroutine 3

Slide 9

Slide 9 text

Problem 2 I have closed Activity/Fragment that launched a coroutine and now its result is no longer needed and its operation should be cancelled.

Slide 10

Slide 10 text

10 ViewModelScope LifecycleScope class MyViewModel: ViewModel() { init { viewModelScope.launch { // Coroutine that will be canceled when the ViewModel is cleared. } } } class MyFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { // Coroutine that will be canceled when when the Lifecycle is destroyed. } } }

Slide 11

Slide 11 text

Problem 3 Waiting for a result from a single-shot callback API and to return the result to the caller

Slide 12

Slide 12 text

12 suspend fun fetchDataFromAPI(): SomeAPIResponse? = suspendCoroutine { continuation -> val callback = object : Callback { override fun onFailure(call: Call,t: Throwable) { continuation.resume(null) } override fun onResponse( call: Call, response: Response ) { continuation.resume(response.body()) } } retrofit.create(ApiService:: class.java).fetchAPIData().enqueue(callback) }

Slide 13

Slide 13 text

Problem 4 Multi-shot callback APIs

Slide 14

Slide 14 text

14 Flows fun simple(): Flow = flow { for (i in 1..3) { delay(100) // pretend we are asynchronously waiting 100 ms emit(i) // emit next value } } fun main() = runBlocking { simple().collect { value -> delay(300) // pretend we are processing it for 300 ms println(value) } }

Slide 15

Slide 15 text

Problem 5 Exception Handling

Slide 16

Slide 16 text

CancellationException 16 fun main() = runBlocking { val job = launch(handler) { throw CancellationException() } job.join() println("Done!!") } fun main() = runBlocking { val job = launch { throw Exception() } job.join() println("Done!!") }

Slide 17

Slide 17 text

17 fun main() = runBlocking { val job = launch { try { println("Performing network request in Coroutine") delay(1000) } catch (e: Exception) { println("Handled exception in Coroutine") } println("Coroutine still running ... ") } delay(500) job.cancel() }

Slide 18

Slide 18 text

18 fun main() = runBlocking { val job = launch { try { println("Performing network request in Coroutine") delay(1000) } catch (e: HttpException) { println("Handled exception in Coroutine") } println("Coroutine still running ... ") } delay(500) job.cancel() }

Slide 19

Slide 19 text

19 fun main() = runBlocking { val job = launch { try { println("Performing network request in Coroutine") delay(1000) } catch (e: Exception) { if (e is CancellationException) { throw e } println("Handled exception in Coroutine") } println("Coroutine still running ... ") } delay(500) job.cancel() }

Slide 20

Slide 20 text

“ 20 Don’t catch CancellationExceptions

Slide 21

Slide 21 text

21 fun main() { val topLevelScope = CoroutineScope(Job()) topLevelScope.launch { try { launch { throw RuntimeException("RuntimeException in nested coroutine") } } catch (exception: RuntimeException) { println("Handle $exception") } } Thread.sleep(100) }

Slide 22

Slide 22 text

22 fun main() { val exceptionHandler = CoroutineExceptionHandler { _, exception -> println("CoroutineExceptionHandler got $exception") } val topLevelScope = CoroutineScope(Job()) topLevelScope.launch(exceptionHandler) { launch { throw RuntimeException("RuntimeException in nested coroutine") } } Thread.sleep(100) }

Slide 23

Slide 23 text

23 ▷ SupervisorJob ▷ supervisorScope

Slide 24

Slide 24 text

Problem 6 Shared mutable state

Slide 25

Slide 25 text

25 Send logs to server Clear logs Add logs log 1 log 2 log 1 log 2 log 3 log 1 log 2 log 3 Add logs Add logs log 1 log 2 log 3 log 4 log 4 is never sent to server

Slide 26

Slide 26 text

26 class LogSynchroniser( ... ) : CoroutineScope { private val mutex = Mutex() ... suspend fun addLogs(logs: List) { mutex.withLock { itemsQueue.addAll(logs) } } fun startSync() { launch { for (event in timer) { mutex.withLock { // Sends logs to server itemsQueue.clear() } } } } }

Slide 27

Slide 27 text

Thanks! Any questions? You can find me at: Github: yoyocoder Twitter: @yoyocoder Email: [email protected] 27