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

Introduction to Kotlin Coroutines

Avatar for Sevil Sevil
March 19, 2019

Introduction to Kotlin Coroutines

Coroutines is full of possibilities and offers new ways of working with background tasks, resulting in more simple, elegant and clean code. In this presentation we will talk about coroutines with the focus of understanding how to get rid of threads and callback patterns. Get ready to be amazed with the new world of clean coding that coroutines opens up to everyone.

Avatar for Sevil

Sevil

March 19, 2019
Tweet

More Decks by Sevil

Other Decks in Programming

Transcript

  1. HOW TO SAY GOODBYE TO THREADING AND CALLBACK HELL KOTLIN

    COROUTINES ON ANDROID Sevil Guler - 12.04.2019
  2. WHAT IS THE PROBLEM progress.visibility = View.VISIBLE userService.doLoginAsync(username, password) {

    user -> userService.requestCurrentFriendsAsync(user) { friends -> val finalUser = user.copy(friends = friends) toast("User ${finalUser.name} has ${finalUser.friends.size} friends") progress.visibility = View.GONE } } Sevil Guler - 12.04.2019 Indentation and number of the callbacks increase with each call that makes the Code less readable and maintainable
  3. WHAT IS THE PROBLEM 1. Do the second friends request

    after the first one - Easy one 2. Run both requests at the same time and find a way to synchronize the callback results. - Complicated Sevil Guler - 12.04.2019 What if you need to get also suggested friends and merge it with current friends which solution you would choose: Assume:
  4. WHAT IS THE PROBLEM progress.visibility = View.VISIBLE userService.doLoginAsync(username, password) {

    user -> userService.requestCurrentFriendsAsync(user) { currentFriends -> userService.requestSuggestedFriendsAsync(user) { suggestedFriends -> val finalUser = user.copy(friends = currentFriends + suggestedFriends) progress.visibility = View.GONE } } } Sevil Guler - 12.04.2019 1. Do the second friends request after the first one
  5. WHAT ARE COROUTINES? ▸ Like threads but better ▸ asynchronous

    code sequentially ▸ can be run using the same thread ▸ light-weight ▸ the limit is almost infinite. ▸ Based on the idea of suspending functions ▸ Safe place where suspending functions will not block the current thread. Sevil Guler - 12.04.2019
  6. WHAT ARE COROUTINES? fun withCoroutines() = runBlocking { repeat(100000) {

    counter -> launch { print(" $counter") } } } fun withThread() = runBlocking { repeat(100000) { counter -> thread { print(" $counter") } } } Sevil Guler - 12.04.2019
  7. WHAT ARE COROUTINES? coroutine { progress.visibility = View.VISIBLE val user

    = suspended { userService.doLogin(username, password) } val currentFriends = suspended { userService.requestCurrentFriends(user) } val finalUser = user.copy(friends = currentFriends) progress.visibility = View.GONE } Coroutine builder Sevil Guler - 12.04.2019
  8. SUSPENDING FUNCTIONS ▸ Block the execution of the coroutine val

    user = suspended { userService.doLogin(username, password) } val currentFriends = suspended { userService.requestCurrentFriends(user) } 
 
 ▸ Can run on the same or a different thread ▸ Only run inside a coroutine or inside another suspending function. ▸ suspend reserved word Sevil Guler - 12.04.2019
  9. SUSPENDING FUNCTIONS suspend fun suspendingFunction() : Int { // Long

    running task return 0 } coroutine { progress.visibility = View.VISIBLE …. } PROBLEM Sevil Guler - 12.04.2019
  10. COROUTINE CONTEXT ▸ Set of rules and configurations ▸ Dispatcher

    ✴ Explicitly: we manually set the dispatcher that will be used ✴ By the coroutine scope Sevil Guler - 12.04.2019
  11. PROBLEM coroutine(Dispatchers.Main) { progress.visibility = View.VISIBLE ... val user =

    suspended { userService.doLogin(username, password) } val currentFriends = suspended { userService.requestCurrentFriends(user) } … } Sevil Guler - 12.04.2019
  12. WITHCONTEXT suspend fun suspendedLgn(name,passwrd) = withContext(Dispatchers.IO){ userService.doLogin(.., ..) } coroutine(Dispatchers.Main)

    { progress.visibility = View.VISIBLE val user = withContext(Dispatchers.IO) { userService.doLogin(username, password) } val currentFriends = withContext(Dispatchers.IO){ userService.requestCurrentFriends(user) } } Sevil Guler - 12.04.2019
  13. DISPATCHERS ▸ Thread or Threads ▸ 1 thread can run

    multiple coroutines Sevil Guler - 12.04.2019
  14. HOW TO RUN COROUTINES - COROUTINES BUILDER ▸ runBlocking ▸

    blocks the current thread 
 ▸ very helpful to test suspending tasks 
 fun testSuspendingFunction() = runBlocking { val res = suspendingTask1() assertEquals(0, res) } Sevil Guler - 12.04.2019
  15. HOW TO RUN COROUTINES - COROUTINES BUILDER LAUNCH GlobalScope.launch {

    ….. } ▸ launch ▸ main builder ▸ won’t block the current thread ▸ always needs a scope ▸ returns a Job Sevil Guler - 12.04.2019
  16. JOBS HAVE A COUPLE OF INTERESTING FUNCTIONS ▸ job.join ▸

    job.cancel val job = GlobalScope.launch(Dispatchers.Main) { doCoroutineTask() val res1 = suspendingTask1() val res2 = suspendingTask2() process(res1, res2) } job.join() Sevil Guler - 12.04.2019
  17. ASYNC GlobalScope.launch(Dispatchers.Main + CoroutineExceptionHandler{ }) { val user = withContext(Dispatchers.IO)

    { userService.doLogin(username, password) } val currentFriends = withContext(Dispatchers.IO{ userService.requestCurrentFriends(user) } val suggestedFriends = withContext(Dispatchers.IO { userService.requestSuggestedFriends(user) } val finalUser = user.copy(friends = currentFriends + suggestedFriends) } Sevil Guler - 12.04.2019
  18. ASYNC GlobalScope.launch(Dispatchers.Main) { val user = withContext(Dispatchers.IO) { userService.doLogin(username, password)

    } val currentFriends = async(Dispatchers.IO) { userService.requestCurrentFriends(user) } val suggestedFriends = async(Dispatchers.IO) { userService.requestSuggestedFriends(user) } val finalUser =currentFriends.await()+ suggestedFriends.await() } Sevil Guler - 12.04.2019
  19. ASYNC ▸ several background tasks in parallel ▸ not a

    suspending function itself ▸ returns a specialized job that is called Deferred ▸ await(), which is the blocking one Sevil Guler - 12.04.2019
  20. GLOBAL SCOPE GlobalScope.launch { ….. } ▸ App lifecycle ▸

    Think twice ▸ specific screen or component Sevil Guler - 12.04.2019
  21. IMPLEMENT COROUTINESCOPE ▸ override the coroutineContext ▸ configure: the dispatcher,

    and the job ▸ to identify the default dispatcher that the coroutines will use ▸ cancel all pending coroutines at any moment ▸ plus(+) operation -> CombinedContext the job is used as the parent of the coroutines created from this scope. Dispatchers.Main + Dispatchers.IO == Dispatchers.IO Sevil Guler - 12.04.2019
  22. IMPLEMENT COROUTINESCOPE class MainActivity : AppCompatActivity(), CoroutineScope { private lateinit

    var job : Job override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) job = Job() } override fun onDestroy() { super.onDestroy() job.cancel() } } Sevil Guler - 12.04.2019
  23. COROUTINE CANCELATION val job = launch { try { repeat(1000)

    { counter -> print(" $counter") delay(50L) } } catch (e: CancellationException) { print(" CancellationException\n") } } delay(1000L) job.cancel() job.join() Sevil Guler - 12.04.2019
  24. EXCEPTIONS ▸ An exception other than CancellationException in a coroutine

    scope cancels its parent. ▸ CoroutineExceptionHandler val handler = CoroutineExceptionHandler { _, exception -> println("Caught $exception") } ▸ If we want cancellation to be propagated only downwards we can use SupervisorJob or supervisorScope. Sevil Guler - 12.04.2019
  25. EXCEPTIONS fun main() = runBlocking { val handler = CoroutineExceptionHandler

    { _, exception -> println("Caught $exception with suppressed ${exception.suppressed.contentToString()}") } val job = GlobalScope.launch(handler) { launch { try { delay(Long.MAX_VALUE) } finally { throw ArithmeticException() } } launch { delay(100) throw IOException() } delay(Long.MAX_VALUE) } job.join() } Caught java.io.IOException with suppressed [java.lang.ArithmeticException] Sevil Guler - 12.04.2019
  26. EXCEPTIONS val supervisor = SupervisorJob() with(CoroutineScope(supervisor)) { val child1 =

    launch(handler) { println("Child1 is failing") throw AssertionError("child1 cancelled") } val child2 = launch { child1.join() println("Child1 cancelled: ${child1.isCancelled}") println("Child2 isActive: $isActive") try { delay(Long.MAX_VALUE) } finally { println("Finally Child2 isActive: $isActive") } } child1.join() println("Cancelling supervisor") supervisor.cancel() child2.join() } Child1 is failing Caught java.lang.AssertionError: child1 cancelled Cancelling supervisor Child1 cancelled: true Child2 isActive: true Finally Child2 isActive: false Sevil Guler - 12.04.2019
  27. WHAT IS NEXT? ▸ Channels ▸ Shared mutable state and

    concurrency ▸ Reactive streams with coroutines …. Sevil Guler - 12.04.2019