Use Kotlin Coroutines in your Android App✍

0

Google Codelabs

목록 보기
14/14
post-thumbnail

Use Kotlin Coroutines in your Android App

이 포스팅은 아래 구글 코드랩을 개인 학습용으로 정리한 글입니다.

1. Before you begin

  • Kotlin Coroutines
    - recommended way of managing background threads
    • can simplify code by reducing the need for callbacks
    • converts async callbacks for long-running tasks
  • callback-based code
// Async callbacks
networkRequest { result ->
   // Successful network request
   databaseSave(result) { rows ->
     // Result saved
   }
}
  • code using coroutines
// The same code with coroutines
val result = networkRequest()
// Successful network request
databaseSave(result)
// Result saved

2. Getting set up

3. Run the starting sample app

  • This starter app
    - uses threads to increment the count after a short delay after you press the screen
    - also fetches a new title from the network and displays it on screen

  • MainActivity
    - displays the UI
    • registers click listeners
    • can display a Snackbar
    • passes events to MainViewModel
    • updates the screen based on LiveData in MainViewModel
  • MainViewModel
    - handles events in onMainViewClicked
    • communicate to MainActivity using LiveData.
  • Executors
    - defines BACKGROUND
    -> can run things on a background thread
  • TitleRepository
    - fetches results from the network and saves them to the database

Adding coroutines to a project

  • kotlinx-coroutines-core
    - Main interface for using coroutines in Kotlin

  • kotlinx-coroutines-android
    - Support for the Android Main thread in coroutines

  • The starter app already includes the dependencies in build.gradle

4. Coroutines in Kotlin

  • For your app to display to the user without any visible pauses, the main thread has to update the screen roughly every 16ms, which is about 60 frames per second.

The callback pattern

  • an example of the callback pattern
// Slow request with callbacks
@UiThread
fun makeNetworkRequest() {
    // The slow network request runs on another thread
    slowFetch { result ->
        // When the result is ready, this callback will get the result
        show(result)
    }
    // makeNetworkRequest() exits after calling slowFetch without waiting for the result
}
  • this code is annotated with @UiThread
    -> it must run fast enough to execute on the main thread

  • However, since slowFetch will take seconds or even minutes to complete
    -> the main thread can't wait for the result.

  • The show(result) callback allows slowFetch to run on a background thread and return the result when it's ready.

Using coroutines to remove callbacks

  • callbacks don't allow the use of some language features (ex. exceptions)

  • The keyword suspend:
    Kotlin's way of marking a function, or function type, available to coroutines.

  • it suspends execution until the result is ready then it resumes where it left off with the result.

  • While it's suspended waiting for a result, it unblocks the thread that it's running on so other functions or coroutines can run.

// Slow request with coroutines
@UiThread
suspend fun makeNetworkRequest() {
    // slowFetch is another suspend function
    // instead of blocking the main thread, 
    // makeNetworkRequest will `suspend` until the result is ready
    val result = slowFetch()
    // continue to execute after the result is ready
    show(result)
}

// slowFetch is main-safe using coroutines
suspend fun slowFetch(): SlowResult { ... }

- The suspend keyword doesn't specify the thread code runs on.
**- Suspend functions may run on a background thread or the main thread

  • Due to its sequential style, it's easy to chain several long running tasks without creating multiple callbacks.
// Request data from network and save it to database with coroutines

// Because of the @WorkerThread, this function cannot be called on the
// main thread without causing an error.
@WorkerThread
suspend fun makeNetworkRequest() {
    // slowFetch and anotherFetch are suspend functions
    val slow = slowFetch()
    val another = anotherFetch()
    // save is a regular function and will block this thread
    database.save(slow, another)
}

// slowFetch is main-safe using coroutines
suspend fun slowFetch(): SlowResult { ... }
// anotherFetch is main-safe using coroutines
suspend fun anotherFetch(): AnotherResult { ... }

5. Controlling the UI with coroutines

Understanding CoroutineScope

  • all coroutines run inside a CoroutineScope
    - A scope controls the lifetime of coroutines through its job
    • When you cancel the job of a scope, it cancels all coroutines started in that scope
  • Scopes also allow you to specify a default dispatcher.
    - A dispatcher controls which thread runs a coroutine.

  • For coroutines started by the UI, it is typically correct to start them on Dispatchers.Main
    - Dispatchers.Main: the main thread on Android

  • Since a ViewModel coroutine almost always updates the UI on the main thread,
    starting coroutines on the main thread saves you extra thread switches.

  • ⚡A coroutine started on the Main thread can switch dispatchers any time after it's started.
    -> can easily switch threads at any time and pass results back to the original thread

Using viewModelScope

  • The AndroidX lifecycle-viewmodel-ktx library adds a CoroutineScope to ViewModels
    - adds a viewModelScope as an extension function of the ViewModel class
  • This scope is bound to Dispatchers.Main
  • will automatically be cancelled when the ViewModel is cleared.

build.gradle (Module: start)

dependencies {
  ...
  // replace x.x.x with latest version
  implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:x.x.x"
}

Switch from threads to coroutines

MainViewModel.kt

/**
* Wait one second then update the tap count.
*/
private fun updateTaps() {
   // TODO: Convert updateTaps to use coroutines
   tapCount++
   BACKGROUND.submit {
       Thread.sleep(1_000)
       _taps.postValue("$tapCount taps")
   }
}
  • This code uses the BACKGROUND ExecutorService (defined in util/Executor.kt) to run in a background thread.

util/Executor.kt

import java.util.concurrent.Executors

/**
 * An executor service that can run [Runnable]s off the main thread.
 */
val BACKGROUND = Executors.newFixedThreadPool(2)

✍...

profile
Be able to be vulnerable, in search of truth

0개의 댓글