Slide 1

Slide 1 text

Clean Architecture in Action Part 2 - Coroutines Saeed Masoumi

Slide 2

Slide 2 text

Async Programming

Slide 3

Slide 3 text

Threading • Threads aren’t cheap • Allocate and initialize a large block of memory • Context switches are costly

Slide 4

Slide 4 text

Threading • Threads aren’t cheap • Threads aren’t easy to debug

Slide 5

Slide 5 text

Threading • Threads aren’t cheap • Threads aren’t easy to debug • Threads aren’t infinite

Slide 6

Slide 6 text

Threading • Threads aren’t cheap • Threads aren’t easy to debug • Threads aren’t infinite

Slide 7

Slide 7 text

Callbacks fun sendFavoriteSong(/*..*/) { prepareAsync { sendAsync { //... } } }

Slide 8

Slide 8 text

Callbacks fun sendFavoriteSong(/*..*/) { prepareAsync { sendAsync { anotherAsync { //.. } } } }

Slide 9

Slide 9 text

Callbacks fun sendFavoriteSong(/*..*/) { prepareAsync { sendAsync { anotherAsync { anotherAsync { //... } } } } }

Slide 10

Slide 10 text

Callbacks fun sendFavoriteSong(/*..*/) { prepareAsync { sendAsync { anotherAsync { anotherAsync { anotherHellAsync { } } } } } }

Slide 11

Slide 11 text

Callbacks • Callbacks hell (A.K.A Christmas tree)

Slide 12

Slide 12 text

Callbacks • Callbacks hell (A.K.A Christmas tree) • Complicated error handling

Slide 13

Slide 13 text

Callbacks • Callbacks hell (A.K.A Christmas tree) • Complicated error handling

Slide 14

Slide 14 text

Promises task { 1 + 1 } then { i -> "result: $i" } success { msg -> println(msg) }

Slide 15

Slide 15 text

Promises • Compositional model with chained calls • Error handling can be complicated

Slide 16

Slide 16 text

Reactive Extensions • Stream and Observable • Nice error handling

Slide 17

Slide 17 text

Coroutines

Slide 18

Slide 18 text

Coroutines • Lightweight threads • Suspendable computation • Platform independent

Slide 19

Slide 19 text

Coroutines fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L) } Hello, World!

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Coroutines fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L) } Non-blocking delay

Slide 23

Slide 23 text

Coroutines fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L) }

Slide 24

Slide 24 text

Coroutines fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L) } Prints Hello,

Slide 25

Slide 25 text

Coroutines fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L) }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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!

Slide 29

Slide 29 text

Structured Concurrency

Slide 30

Slide 30 text

Structured Concurrency Ran out of memory Forget to keep reference Too long delay Join them? • Threads are global

Slide 31

Slide 31 text

Structured Concurrency • Threads are global • GlobalScope is global too!

Slide 32

Slide 32 text

Structured Concurrency • Threads are global • GlobalScope is global too!

Slide 33

Slide 33 text

Structured Concurrency fun downloadAPKs(packageNames: List) { packageNames.forEach { val url = findUrl(it) GlobalScope.launch { download(url) //... } } }

Slide 34

Slide 34 text

Structured Concurrency fun downloadAPKs(packageNames: List) { packageNames.forEach { val url = findUrl(it) GlobalScope.launch { download(url) //... } } } Crash!!!

Slide 35

Slide 35 text

Structured Concurrency So instead of launching in global scope, run in a specific scope

Slide 36

Slide 36 text

Structured Concurrency suspend fun downloadAPKs(packageNames: List) = coroutineScope { packageNames.forEach { val url = findUrl(it) launch { download(url) //... } } }

Slide 37

Slide 37 text

Structured Concurrency suspend fun downloadAPKs(packageName: List) = coroutineScope { packageName.forEach { val url = findUrl(it) launch { download(url) //... } } }

Slide 38

Slide 38 text

Structured Concurrency suspend fun downloadAPKs(packageName: List) = coroutineScope { packageName.forEach { val url = findUrl(it) launch { download(url) //... } } } Crash Cancels Waits for all previous “launch” to complete No Job leak anymore

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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.

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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") }

Slide 44

Slide 44 text

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() { }

Slide 45

Slide 45 text

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") }

Slide 46

Slide 46 text

Cancellation

Slide 47

Slide 47 text

Cancellation fun main(args: Array) = 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}

Slide 48

Slide 48 text

Dispatchers • Like RxJava’s Schedulers • IO • Main (Android, JavaFX, Swing) • Default • Unconfined

Slide 49

Slide 49 text

Async/Await

Slide 50

Slide 50 text

Async/Await suspend fun calculateOne(): Int { delay(1000L) return 11 } suspend fun calculateTwo(): Int { delay(1000L) return 22 } Concurrent Execution !?

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Experimental Features • Channels • Select Expression

Slide 55

Slide 55 text

Coroutines + Android

Slide 56

Slide 56 text

Back to our App

Slide 57

Slide 57 text

Domain interface TVShowRepository { fun getAll(): List }

Slide 58

Slide 58 text

Domain Layer interface TVShowRepository { suspend fun getAll(): List }

Slide 59

Slide 59 text

Domain Layer interface TVShowRepository { suspend fun getAll(): List } What about exceptions? - Try/Catch - Either

Slide 60

Slide 60 text

Either https://arrow-kt.io/docs/datatypes/either/ sealed class Either : EitherOf

Slide 61

Slide 61 text

Either https://arrow-kt.io/docs/datatypes/either/ sealed class Either : EitherOf - Call it “left” - Put Error/Exception classes in this Generic

Slide 62

Slide 62 text

Either https://arrow-kt.io/docs/datatypes/either/ sealed class Either : EitherOf - Call it “right” - The success Value

Slide 63

Slide 63 text

Domain Layer interface TVShowRepository { suspend fun getAll(): List }

Slide 64

Slide 64 text

Domain Layer interface TVShowRepository { suspend fun getAll(): Either> } sealed class Error(val msg: String) { class NetworkConnection(msg: String) : Error(msg) //.. sealed class Feature(msg: String) : Error(msg) { class UnreachableTVShow : Feature("UnReachable") //.. } }

Slide 65

Slide 65 text

Domain Layer when (error) { is NetworkConnection -> { /*..*/} is UnreachableTVShow -> { /*..*/} else -> { /*..*/} } Looks better?

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

What happened? https://github.com/gildor/kotlin-coroutines-retrofit • All non-UI layers are now implemented with suspend functions • Sequential programming • More readable code

Slide 68

Slide 68 text

But… • Structured concurrency on UI layer

Slide 69

Slide 69 text

ViewModel abstract class BaseViewModel : ViewModel()

Slide 70

Slide 70 text

ViewModel abstract class BaseViewModel : ViewModel(), CoroutineScope

Slide 71

Slide 71 text

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() } }

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

ViewModel class TVShowViewModel(val repository: TVShowRepository) : BaseViewModel() { fun load() = launch { val result = async(Dispatchers.IO) { repository.getAll() }.await() //do whatever you want with result } }

Slide 74

Slide 74 text

Clean Architecture in Action twitter.com/s_masoumi
 
 github.com/SaeedMasoumi