Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin Coroutines - How to use it right

Kotlin Coroutines - How to use it right

Shared at AndroidCafe.in Meetup #17 - International Women Day Celebration

Yoyo Coder

May 21, 2021
Tweet

More Decks by Yoyo Coder

Other Decks in Programming

Transcript

  1. Kotlin Coroutines - How
    to use it right

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  5. 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!!

    View full-size slide

  6. 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

    View full-size slide

  7. 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.

    View full-size slide

  8. 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.
    }
    }
    }

    View full-size slide

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

    View full-size slide

  10. 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)
    }

    View full-size slide

  11. Problem 4
    Multi-shot callback APIs

    View full-size slide

  12. 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)
    }
    }

    View full-size slide

  13. Problem 5
    Exception Handling

    View full-size slide

  14. 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!!")
    }

    View full-size slide

  15. 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()
    }

    View full-size slide

  16. 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()
    }

    View full-size slide

  17. 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()
    }

    View full-size slide


  18. 20
    Don’t catch CancellationExceptions

    View full-size slide

  19. 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)
    }

    View full-size slide

  20. 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)
    }

    View full-size slide

  21. 23
    ▷ SupervisorJob
    ▷ supervisorScope

    View full-size slide

  22. Problem 6
    Shared mutable state

    View full-size slide

  23. 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

    View full-size slide

  24. 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()
    }
    }
    }
    }
    }

    View full-size slide

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

    View full-size slide