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

Clean Architecture in Action [part 2]- Coroutines - Saeed Masoumi

Clean Architecture in Action [part 2]- Coroutines - Saeed Masoumi

Saeed Masoumi

October 18, 2018
Tweet

More Decks by Saeed Masoumi

Other Decks in Programming

Transcript

  1. Threading • Threads aren’t cheap • Allocate and initialize a

    large block of memory • Context switches are costly
  2. Promises task { 1 + 1 } then { i

    -> "result: $i" } success { msg -> println(msg) }
  3. Coroutines fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,")

    Thread.sleep(2000L) } • Launch new coroutine in background and continue • Returns a job
  4. Coroutines fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,")

    Thread.sleep(2000L) } CoroutineScope with application lifetime
  5. Coroutines fun main() = runBlocking { GlobalScope.launch { delay(1000L) println("World!")

    } println("Hello,") delay(2000L) } Blocks until the coroutine inside runBlocking completes
  6. Coroutines fun main() = runBlocking { val job = GlobalScope.launch

    { delay(1000L) println("World!") } println("Hello,") job.join() } wait until child coroutine completes
  7. Coroutines fun main() = runBlocking { GlobalScope.launch { delay(1000L) println("World!")

    } println("Hello,") } fun main() = runBlocking { launch { delay(1000L) println("World!") } println("Hello,") } Hello, Hello, World!
  8. Structured Concurrency Ran out of memory Forget to keep reference

    Too long delay Join them? • Threads are global
  9. Structured Concurrency fun downloadAPKs(packageNames: List<String>) { packageNames.forEach { val url

    = findUrl(it) GlobalScope.launch { download(url) //... } } } Crash!!!
  10. Structured Concurrency suspend fun downloadAPKs(packageName: List<String>) = coroutineScope { packageName.forEach

    { val url = findUrl(it) launch { download(url) //... } } } Crash Cancels Waits for all previous “launch” to complete No Job leak anymore
  11. Structured Concurrency fun main() = runBlocking { launch { delay(200L)

    println("Task from runBlocking") } coroutineScope { launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") } println("Coroutine scope is over") } Task from coroutine scope Task from runBlocking Task from nested launch Coroutine scope is over
  12. Structured Concurrency fun main() = runBlocking { launch { delay(200L)

    println("Task from runBlocking") } coroutineScope { launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") } println("Coroutine scope is over") } launch new coroutine in the scope of runBlocking
  13. Structured Concurrency fun main() = runBlocking { launch { delay(200L)

    println("Task from runBlocking") } coroutineScope { launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") } println("Coroutine scope is over") } - Creates a new coroutine scope - Does not block the current thread while waiting for all children to complete.
  14. Structured Concurrency fun main() = runBlocking { launch { delay(200L)

    println("Task from runBlocking") } coroutineScope { launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") } println("Coroutine scope is over") } Waits nested launch completes
  15. Structured Concurrency fun main() = runBlocking { launch { delay(200L)

    println("Task from runBlocking") } coroutineScope { launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") } println("Coroutine scope is over") }
  16. Structured Concurrency fun main() = runBlocking { launch { delay(200L)

    println("Task from runBlocking") } coroutineScope { launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") } println("Coroutine scope is over") } suspend fun doWork() { }
  17. Structured Concurrency fun main() = runBlocking { launch { delay(200L)

    println("Task from runBlocking") } doWork() println("Coroutine scope is over") } suspend fun doWork() = coroutineScope { launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") }
  18. Cancellation fun main(args: Array<String>) = runBlocking { val job =

    launch { doWork() } delay(1000) job.cancelAndJoin() } private suspend fun doWork() { try { delay(2000L) } catch (e: Exception) { println(e) } } kotlinx.coroutines.JobCancellationException: Job was cancelled; job=StandaloneCoroutine{Cancelling}
  19. Async/Await suspend fun calculateOne(): Int { delay(1000L) return 11 }

    suspend fun calculateTwo(): Int { delay(1000L) return 22 } Concurrent Execution !?
  20. Async/Await fun main() = runBlocking { val time = measureTimeMillis

    { val one = async { calculateOne() } val two = async { calculateTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") } ~ 1 sec Like launch but return Deferred
  21. Async/Await fun main() = runBlocking { val time = measureTimeMillis

    { val one = async(start = CoroutineStart.LAZY) { calculateOne() } val two = async(start = CoroutineStart.LAZY) { calculateTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") } ~ 2 sec
  22. Async/Await fun main() = runBlocking { val time = measureTimeMillis

    { val one = async(start = CoroutineStart.LAZY) { calculateOne() } val two = async(start = CoroutineStart.LAZY) { calculateTwo() } one.start() two.start() println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") } ~ 1 sec
  23. Either https://arrow-kt.io/docs/datatypes/either/ sealed class Either<out A, out B> : EitherOf<A,

    B> - Call it “left” - Put Error/Exception classes in this Generic
  24. Domain Layer interface TVShowRepository { suspend fun getAll(): Either<Error, List<TVShow>>

    } sealed class Error(val msg: String) { class NetworkConnection(msg: String) : Error(msg) //.. sealed class Feature(msg: String) : Error(msg) { class UnreachableTVShow : Feature("UnReachable") //.. } }
  25. Domain Layer when (error) { is NetworkConnection -> { /*..*/}

    is UnreachableTVShow -> { /*..*/} else -> { /*..*/} } Looks better?
  26. Data Layer override suspend fun getAll(api: api) { try {

    val users = api.getAll().await() return Either.right(users) } catch (e: Exception) { return Either.left(mapToError(e)) } } https://github.com/gildor/kotlin-coroutines-retrofit
  27. What happened? https://github.com/gildor/kotlin-coroutines-retrofit • All non-UI layers are now implemented

    with suspend functions • Sequential programming • More readable code
  28. ViewModel abstract class BaseViewModel : ViewModel(), CoroutineScope { protected val

    job: Job = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onCleared() { super.onCleared() job.cancel() } }
  29. ViewModel abstract class BaseViewModel : ViewModel(), CoroutineScope { protected val

    job: Job = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onCleared() { super.onCleared() job.cancel() } } Plus operator Launch on main thread Job will reference to all launches
  30. ViewModel class TVShowViewModel(val repository: TVShowRepository) : BaseViewModel() { fun load()

    = launch { val result = async(Dispatchers.IO) { repository.getAll() }.await() //do whatever you want with result } }