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

Suspend your routine with Kotlin

Suspend your routine with Kotlin

A presentation about Kotlin coroutines concepts. It covers:

- The internal state machine of a coroutine
- Difference between suspend and blocking calls
- What's a suspend function and it's internals on the JVM
- Context and relevant Scopes on Android
- Structured Concurrency
- Flows, context preservation, backpressure
- SharedFlows
- How to migrate, converting callbacks to suspend functions or flows

Avatar for Alexandru Gabor

Alexandru Gabor

September 17, 2023
Tweet

Other Decks in Programming

Transcript

  1. What’s a Coroutine • Like threads, but not really. •

    State machine lifecycleScope.launch { // State 0 firstSuspendFunction() // State 1 secondSuspendFunction() // State 2 thirdSuspendFunction() // State 3 }
  2. Suspend vs Blocking • `suspend fun` may suspend its execution

    • Blocking: Thread.sleep(), IO operations ◦ Frees CPU ◦ Thread waits to resume • Suspend: ◦ Lets thread execute something else in the meantime. lifecycleScope.launch { delay(1000) // suspends 1s Thread.sleep(1000) // blocks the thread 1s }
  3. Suspend • It’s actually a callback • Sequential • No

    callback hell fun mySuspendFunction( param: Int, completion: Continuation<Any?> ): Result { ... completion.resume(result) ... }
  4. Starting a Coroutine • launch • async val one =

    lifecycleScope.async { doSomethingUsefulOne() } val two = lifecycleScope.async { doSomethingUsefulTwo() } lifecycleScope.launch { one.await() + two.await() }
  5. Scope • A Coroutine has to start on a Scope

    • Wraps the Context • Represents a lifecycle ◦ viewModelScope ◦ lifecycleScope class ViewModel() { val viewModelScope = CoroutineScope(...) override fun onCleared() { viewModelScope.cancel() } }
  6. Structured Concurrency Cancels execution when: • Not needed anymore •

    On error val one = lifecycleScope.async { doSomethingUsefulOne() } val two = lifecycleScope.async { doSomethingUsefulTwo() } lifecycleScope.launch { one.await() + two.await() }
  7. Context • Dispatchers ◦ Main ◦ Default ◦ IO ◦

    newSingleThreadContext • Jobs ◦ Supervisor • CoroutineName CoroutineScope(Dispatchers.IO + SupervisorJob())
  8. Context • withContext • convention lifecycleScope.launch { showLoading() // Running

    in Main thread withContext(Dispatchers.IO) { // Running in IO thread pool readFromDb() } updateUi() // Running in Main thread }
  9. Flow • Flows are cold • Cancelled with the scope

    fun simple(): Flow<Int> = flow { // flow builder for (i in 1..3) { ... emit(i) // emit next value } } lifecycleScope.launch { // Collect the flow simple().collect { value -> println(value) } }
  10. Flow context preservation • Runs in collector’s context • flowOn

    changes context upstream getFlow().filter { // Running on IO }.map { // Running on IO } .flowOn(Dispatchers.IO) .onEach { // Running on Main thread } .launchIn(lifecycleScope)
  11. Flow backpressure • Suspend • Buffer: buffer() • Latest: conflate()

    • Drop: dropWhenBusy() fun simple(): Flow<Int> = flow { for (i in 1..3) { ... // Takes X sec emit(i) // Produce value } } // Consume value simple().collect { value -> ... // Takes Y sec println(value) }
  12. StateFlow private val _counter = MutableStateFlow(0) // private mutable state

    flow val counter: StateFlow<Int> get() = _counter // exposed as read-only state flow fun inc() { _counter.value++ // convenient state update }
  13. SharedFlow // no buffer, rendezvous with subscribers private val _events

    = MutableSharedFlow<Event>(0) // expose as a plain flow public val events: Flow<Event> get() = _events suspend fun produceEvent(event: Event) { _events.emit(event) // suspends until all subscribers receive it }
  14. StateFlow vs SharedFlow // MutableStateFlow(initialValue) is a shared flow with

    the following parameters: val shared = MutableSharedFlow( replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, ) shared.tryEmit(initialValue) // emit the initial value val state = shared.distinctUntilChanged() // get StateFlow-like behavior
  15. Channel • Suspend queue val channel = Channel<Int>() lifecycleScope.launch {

    ... channel.send(value) } lifecycleScope.launch { ... val result = channel.receive() }
  16. Migration • Jetpack support • Room, WorkManager, Retrofit • Convert

    callbacks to suspend • Convert API to Flow • LiveData and Flow
  17. Convert callbacks to suspend function suspend fun getStuff() = suspendCancellableCoroutine<Result>

    { continuation -> // executes this block and suspends registerCallback(object : Callback { override fun onSuccess(result: Result) { // resumes normally continuation.resume(result) } override fun onFailure(throwable: Throwable) { // resumes and throws exception continuation.resumeWithException(throwable) } }) // cleanup when canceled continuation.invokeOnCancellation { unregister() } }
  18. Convert callbacks to Flow suspend fun getStuff() = callbackFlow<Result> {

    // `this` is a channel registerCallback(object : Callback { override fun onSuccess(result: Result) { offer(result) // Send result } override fun onFailure(throwable: Throwable) { close(throwable) // Send error } override fun onComplete() { close() // Close the stream } }) awaitClose { unregister() } // clean up }
  19. LiveData and Flow val resultLiveData = liveData { ... emit(initalValue)

    emitSource(anotherLiveData) } val resultLiveData = getFlow().asLiveData() val resultflow = getLiveData().asFlow()
  20. Resources • Coroutines guide: https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html • Coroutines on Android codelab:

    https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#0 • Flow & Livedata codelab: https://codelabs.developers.google.com/codelabs/advanced-kotlin-coroutines/#0 • Android coroutines docs: https://developer.android.com/kotlin/coroutines; https://developer.android.com/topic/libraries/architecture/coroutines#livedata • Roman Elizarov - Kotlin Libraries Team Lead: https://medium.com/@elizarov • Manuel Vivo - Dev Rel at Google: https://medium.com/@manuelvicnt • SharedFlow github issue: https://github.com/Kotlin/kotlinx.coroutines/issues/2034 @AlexGabor42