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

Async Programming in Android using Kotlin Corou...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Srijith Srijith
August 29, 2019

Async Programming in Android using Kotlin Coroutines

Avatar for Srijith

Srijith

August 29, 2019
Tweet

Other Decks in Programming

Transcript

  1. COROUTINES WHY COROUTINES? ▸ Light-weight threads ▸ Async code in

    synchronous fashion ▸ Suspends and resumes execution ▸ Performs blocking or long-running tasks concurrently w/o blocking ▸ Promotes Structured Concurrency
  2. COROUTINES fun onSaveProfileClicked() { GlobalScope.launch { val response = sendProfileDetails(view.profileDetails())

    when(response.status) { is Success -> // do something is Failure -> // do something } } } suspend fun sendProfileDetails(profile: Profile): SaveProfileResponse { return api.sendProfileDetails(profile) }
  3. COROUTINES fun onSaveProfileClicked() { GlobalScope.launch { val response = sendProfileDetails(view.profileDetails())

    when(response.status) { is Success -> // do something is Failure -> // do something } } } suspend fun sendProfileDetails(profile: Profile): SaveProfileResponse { return api.sendProfileDetails(profile) }
  4. COROUTINES fun onSaveProfileClicked() { GlobalScope.launch { val response = sendProfileDetails(view.profileDetails())

    when(response.status) { is Success -> // do something is Failure -> // do something } } } suspend fun sendProfileDetails(profile: Profile): SaveProfileResponse { return api.sendProfileDetails(profile) }
  5. BUILDING BLOCKS OF COROUTINES COROUTINE BUILDERS ‣ launch ‣ async

    ‣ runBlocking ‣ produce A way to create coroutines
  6. BUILDING BLOCKS OF COROUTINES Launch ‣ Doesn’t block the current

    thread ‣ Returns a Job ‣ Job - reference to the coroutine
  7. BUILDING BLOCKS OF COROUTINES Method Signature CoroutineScope.launch( context: CoroutineContext =

    EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job GlobalScope.launch { // ... } Usage
  8. BUILDING BLOCKS OF COROUTINES Async ‣ Creates a coroutine and

    returns the future result as Deferred ‣ Deferred gives the future result or throws exception on cancellation ‣ Deferred is a Job ‣ To get the result from Deferred, call await
  9. BUILDING BLOCKS OF COROUTINES Await ‣ Waits for the result

    without blocking the current thread ‣ Suspends the coroutine ‣ Returns the result or throws exception if computation is cancelled
  10. BUILDING BLOCKS OF COROUTINES Method Signature fun <T> CoroutineScope.async( context:

    CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred<T> val deferred: Deferred<User> = async { fetchUserDetails() } //... val user = deferred.await() Usage
  11. BUILDING BLOCKS OF COROUTINES RunBlocking ‣ Runs a coroutine while

    blocking the current thread till it’s completion ‣ Used to bridge blocking code with suspending style code
  12. COROUTINE CONTEXT AND DISPATCHERS COROUTINE CONTEXT ‣ Every Coroutines runs

    in a particular context ‣ CoroutineContext can contain a set of elements ‣ The main elements are Job and Dispatchers ‣ Child inherits parent’s context through CoroutineScope.coroutineContext
  13. COROUTINE CONTEXT Job ‣ A cancellable background job with lifecycle

    that can finish on it’s completion ‣ Launch coroutine builder returns a Job ‣ Jobs can be arranged in parent-child hierarchy
  14. COROUTINE CONTEXT New - Job State fun CoroutineScope.launch( context: CoroutineContext

    = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job val job = GlobalScope.launch(start = CoroutineStart.LAZY) { //... } //... job.start()
  15. COROUTINE CONTEXT Job ‣ Job.cancel() - cancel a coroutine Job

    explicitly val job = GlobalScope.launch { //... } //... job.cancel()
  16. COROUTINE DISPATCHERS COROUTINE DISPATCHER ‣ Determines in which thread the

    coroutine gets executed ‣ Can specify coroutine to run on single thread or a thread pool
  17. COROUTINE DISPATCHERS Types of Dispatchers ‣ Default - A shared

    pool of threads ‣ Main - Main thread in Android ‣ IO - thread pool for blocking IO tasks ‣ Unconfined - Not confined to a specific thread ‣ newSingleThreadContext - Runs in new thread
  18. COROUTINE DISPATCHERS ‣ Child coroutine inherits the context from parent

    unless specified explicitly ‣ So child uses the same dispatcher as parent
  19. COROUTINE CONTEXT COROUTINE CONTEXT ‣ Multiple context elements can be

    passed to coroutine builder using plus operator val deferred = async(Dispatchers.IO + mainScope) { //... }
  20. COROUTINE SCOPE COROUTINE SCOPE ‣ The scope within which a

    coroutine can run ‣ GlobalScope - the coroutine can run till the process gets killed ‣ Every child coroutine runs within the scope of parent coroutine
  21. COROUTINE SCOPE Scope Builders ‣ coroutineScope ‣ Creates scope to

    run a suspending block of code ‣ Inherits context from it’s parent ‣ Returns the return value of the suspending block
  22. COROUTINE SCOPE Scope Builders ‣ MainScope ‣ Useful for UI

    components ‣ Potentially blocking UI computations can be run in this scope
  23. COROUTINE SCOPE Scope Builders ‣ Scopes also can be manually

    created: val scope = CoroutineScope(Dispatchers.Main + appLifecycleAwareJob) ‣ Usage: scope.launch { //… }
  24. SUSPENDING FUNCTIONS ‣ Functions that can execute blocking code without

    blocking current thread ‣ Marked with suspend keyword suspend fun sendProfileDetails(profile: Profile): SaveProfileResponse { //... }
  25. SUSPENDING FUNCTIONS ‣ Can be called either from a coroutine

    or another suspending function ‣ Blocks the coroutine, won’t block current thread ‣ Coroutine builders takes suspending function as parameter CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job
  26. CANCELLATIONS AND TIMEOUTS CANCELLATION ‣ Cancelled using a reference of

    the Job, calling job.cancel() ‣ Computations running within coroutines won’t be cancelled ‣ isActive can be used to check if coroutine is active and continue the execution ‣ Any resource cleanup can be done inside finally {} block
  27. CANCELLATIONS AND TIMEOUTS TIMEOUT ‣ Cancel coroutine after a timeout

    using ‣ withTimeout() and withTimeoutOrNull() ‣ withTimeout() - throws TimeoutCancellationException on timeout ‣ withTimeoutOrNull() - returns null on timeout withTimeout(2000L) { //... }
  28. EXCEPTION HANDLING EXCEPTION HANDLING ‣ Exceptions can be handled using

    try-catch block ‣ Unhandled exceptions can be caught using CoroutineExceptionHandler scope.launch(exceptionHandler) { //.. } val exceptionHandler = CoroutineExceptionHandler { context, throwable -> }
  29. EXCEPTION HANDLING ‣ A better way to handle exceptions is

    by wrapping them in sealed classes sealed class Result<T> class Success<T>(val value: T): Result<T>() class Failure<T>(val t: Throwable): Result<T>()
  30. COMPOSITION ‣ Suspending functions are invoked in a sequential fashion

    scope.launch { val firstHalf = getFirstHalfScore() val secondHalf = getSecondHalfScore() val totalScore = firstHalf + secondHalf } suspend fun getFirstHalfScore(): Int { delay(1000) return 2 } suspend fun getSecondHalfScore(): Int { delay(1000) return 1 }
  31. COMPOSITION COMPOSITION ‣ Concurrent execution can be done using async-await

    suspend fun totalScore(): Int = coroutineScope { val firstHalf = async { getFirstHalfScore() } val secondHalf = async { getSecondHalfScore() } firstHalf.await() + secondHalf.await() }
  32. STRUCTURED CONCURRENCY STRUCTURED CONCURRENCY ‣ Coroutines launched in GlobalScope needs

    to be cancelled when current screen is destroyed ‣ So references to every coroutines needs to be maintained
  33. STRUCTURED CONCURRENCY var globalJob: Job? = null scope.launch { globalJob

    = GlobalScope.launch { //.. } } fun onDestroy() { globalJob?.cancel() }
  34. STRUCTURED CONCURRENCY ‣ If the scope of parent coroutine is

    used, then when parent is cancelled, all child coroutines will be cancelled automatically scope.launch { launch { //.. } } fun onDestroy() { scope.cancel() }
  35. STRUCTURED CONCURRENCY ‣ If all coroutines needs to be cancelled

    if one of the them throws an exception ‣ Scoping them within their parent coroutine scope does that ‣ Similarly parent coroutine waits for all of it’s children to complete before it finishes itself
  36. TESTING ‣ Kotlin provides a separate library for testing Coroutines

    ‣ kotlinx-coroutines-test library ‣ Provides following utilities for testing: ‣ TestCoroutineDispatcher ‣ TestCoroutineScope ‣ TestDispatchers
  37. TESTING ‣ TestCoroutineDispatcher ‣ Mocks the coroutine dispatcher ‣ Executes

    coroutines immediately ‣ Provides a DelayController to mock delayed execution
  38. REFERENCES CODE SAMPLE AND REFERENCES ‣ Code sample https://github.com/sjthn/UnderstandingCoroutines ‣

    Kotlin Coroutines Guide (https://kotlinlang.org/docs/reference/coroutines/ coroutines-guide.html) ‣ Roman Elizarov’s KotlinConf talks