Slide 1

Slide 1 text

Coroutines in Practice Mohit Sarveiya /heyitsmohit twitter.com

Slide 2

Slide 2 text

Coroutines in Practice • Convert feature from RxJava to Coroutines • Scopes • Context & Dispatchers

Slide 3

Slide 3 text

Why Coroutines? • Language level support • Multiplatform support - In Progress • Scopes

Slide 4

Slide 4 text

Mac OS App
 (VIPER) Android App
 (MVP) Libraries Problem iOS App
 (VIPER) Libraries Libraries

Slide 5

Slide 5 text

Mac OS App
 (VIPER) Android App
 (MVP) Async
 Logic Goal iOS App
 (VIPER)

Slide 6

Slide 6 text

TV App Android App Document
 Feature Document Feature

Slide 7

Slide 7 text

Document Feature • Gets HTML text from API • Privacy policy • TOS • Pull to refresh • Error state

Slide 8

Slide 8 text

Document Feature • Simple feature • Uses Model View Presenter • Playground to show new patterns & libraries

Slide 9

Slide 9 text

Document Feature View Presenter Model

Slide 10

Slide 10 text

Document Feature View Presenter Model Attached

Slide 11

Slide 11 text

Document Feature View Presenter Model Attached getDocument(uri: String)

Slide 12

Slide 12 text

Document Feature View Presenter Model Document


Slide 13

Slide 13 text

data class Document( @Json(name = "html") val html: String? = null ) Document Model

Slide 14

Slide 14 text

Document Feature View Presenter Model showHtml(html:String) Document


Slide 15

Slide 15 text

Feature in RxJava

Slide 16

Slide 16 text

interface DocumentContract { interface Presenter { fun onViewAttached(view: DocumentContract.View) fun onViewDetached() fun onPullToRefresh() } } MVP Contract

Slide 17

Slide 17 text

interface DocumentContract { interface View { fun showLoadingState() fun showErrorState() fun showRawHtml(rawHtml: String) } } MVP Contract

Slide 18

Slide 18 text

interface Model { fun fetchHtmlDocument(): Single } Model

Slide 19

Slide 19 text

sealed class DocumentResponse { data class Success(val data: String) : DocumentResponse() data class Error(val error: DocumentRequestError) : DocumentResponse() } API Response

Slide 20

Slide 20 text

class DocumentPresenter( val documentModel: DocumentContract.Model, val backgroundScheduler: Scheduler, val foregroundScheduler: Scheduler ) : Presenter { val compositeDisposable = CompositeDisposable() var fetchDocumentDisposable: Disposable? = null } Presenter

Slide 21

Slide 21 text

class DocumentPresenter(…) : Presenter { } Presenter override fun onViewAttached(view: DocumentContract.View) { documentView = view fetchHtmlDocument() } override fun onPullToRefresh() = fetchHtmlDocument()

Slide 22

Slide 22 text

Presenter fun fetchHtmlDocument() { fetchDocumentDisposable = documentModel.fetchHtmlDocument() .subscribeOn(backgroundScheduler) .observeOn(foregroundScheduler) .subscribe { response !-> when (response) { is DocumentResponse.Success !-> 
 documentView!?.showRawHtml(response.data) is DocumentResponse.Error !-> { if (response.documentRequestError !== NoNetwork) { documentView!?.showNoNetworkErrorState() } else { documentView!?.showGenericErrorState() } } } } }

Slide 23

Slide 23 text

Threading fun fetchHtmlDocument() { fetchDocumentDisposable = documentModel.fetchHtmlDocument() .subscribeOn(backgroundScheduler) .observeOn(foregroundScheduler) .subscribe { response !-> when (response) { is DocumentResponse.Success !-> 
 documentView!?.showRawHtml(response.data) is DocumentResponse.Error !-> { if (response.documentRequestError !== NoNetwork) { documentView!?.showNoNetworkErrorState() } else { documentView!?.showGenericErrorState() } } } } }

Slide 24

Slide 24 text

Handle Response fun fetchHtmlDocument() { fetchDocumentDisposable = documentModel.fetchHtmlDocument() .subscribeOn(backgroundScheduler) .observeOn(foregroundScheduler) .subscribe { response !-> when (response) { is DocumentResponse.Success !-> 
 documentView!?.showRawHtml(response.data) is DocumentResponse.Error !-> { if (response.documentRequestError !== NoNetwork) { documentView!?.showNoNetworkErrorState() } else { documentView!?.showGenericErrorState() } } } } }

Slide 25

Slide 25 text

Cancellation val compositeDisposable = CompositeDisposable() override fun onViewDetached() { compositeDisposable.clear() }

Slide 26

Slide 26 text

View class HtmlDocumentActivity : DocumentContract.View { swiperefreshlayout!?.setOnRefreshListener(documentPresenter!::onPullToRefresh) override fun onCreate() { documentPresenter.onViewAttached(this) } override fun onStop() { documentPresenter.onViewDetached() } … }

Slide 27

Slide 27 text

Convert to Coroutines

Slide 28

Slide 28 text

Basics Concepts • Scopes • Context • Job • Dispatchers

Slide 29

Slide 29 text

Coroutine GlobalScope.launch { val document: Document = model.getDocument(uri) view.showHtml(document.html) }

Slide 30

Slide 30 text

interface Model { suspend fun getDocument(): Document } Model

Slide 31

Slide 31 text

Suspend keyword • Allows method to suspend and resume computation • Non blocking

Slide 32

Slide 32 text

Using Suspend Method fun onCreate() { val document: Document = model.getDocument(uri) }

Slide 33

Slide 33 text

Where could you call suspending functions? • Other suspend functions • Coroutine builders Using Suspend Method fun onCreate() { val document: Document = model.getDocument(uri) }

Slide 34

Slide 34 text

Coroutine Builders • Launch • Async • RunBlocking • Actor

Slide 35

Slide 35 text

How to use coroutine builders 1. Need to create a scope
 2. Call any of the builder methods on the scope.

Slide 36

Slide 36 text

What is a Scope? • Specify the lifetime of async operations • Group together async operations

Slide 37

Slide 37 text

Global Scope GlobalScope.launch { val document: Document = model.getDocument(uri) } Depends on app’s life time

Slide 38

Slide 38 text

Job val job: Job = GlobalScope.launch { val document: Document = model.getDocument(uri) } job.cancel()

Slide 39

Slide 39 text

Job States • Active • Completing • Cancelling • Completed

Slide 40

Slide 40 text

Job val job: Job = GlobalScope.launch { val document: Document = model.getDocument(uri) } job.isActive job.isCompleted job.isCancelled

Slide 41

Slide 41 text

Threading val job: Job = GlobalScope.launch { val document: Document = model.getDocument(uri) } Which thread will it run on?

Slide 42

Slide 42 text

Context object GlobalScope : CoroutineScope { val coroutineContext: CoroutineContext }

Slide 43

Slide 43 text

Context Set of elements contains • Job • Dispatcher

Slide 44

Slide 44 text

Dispatcher Specifies which thread the coroutines will run on. • Default Dispatcher • IO • Main • Unconfined

Slide 45

Slide 45 text

Threading GlobalScope.launch { val document: Document = model.getDocument(uri) } Default Dispatcher Thread Pool

Slide 46

Slide 46 text

Threading GlobalScope(Dispatchers.IO).launch { val document: Document = model.getDocument(uri) }

Slide 47

Slide 47 text

Main Thread val scope = CoroutineScope(Dispatchers.Main) scope.launch { }

Slide 48

Slide 48 text

Main Thread val scope = CoroutineScope(Dispatchers.Main) scope.launch { val document = model.getDocument(uri) } Network On Main Thread Exception

Slide 49

Slide 49 text

Switch Threads val scope = CoroutineScope(Dispatchers.Main) scope.launch { val document = withContext(Dispatchers.IO) { 
 return model.getDocument(uri) } }

Slide 50

Slide 50 text

Document Feature View Presenter Model

Slide 51

Slide 51 text

Document Feature View Presenter Model

Slide 52

Slide 52 text

Updating Model 1. Bridge callback to coroutines in the app
 2. Update SDK to use Coroutines with Retrofit • Suspend function • Deferred type

Slide 53

Slide 53 text

Updating Model 1. Bridge callback to coroutines in the app
 2. Update SDK to use Coroutines with Retrofit • Suspend function • Deferred type

Slide 54

Slide 54 text

SDK Model API Networking Library

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

class VimeoClient { Call getDocument(String uri, VimeoCallback callback) { Call call = vimeoService.getDocument(uri); call.enqueue(callback); return call; } … } Getting data from the API

Slide 57

Slide 57 text

suspend fun suspendCancellableCoroutine( block: (CancellableContinuation) !-> Unit ): T Bridge Callbacks to Coroutines

Slide 58

Slide 58 text

interface CancellableContinuation { fun resumeWith(result: Result) fun resume(value: T)
 fun resumeWithException(exception: Throwable) } Bridge Callbacks to Coroutines

Slide 59

Slide 59 text

suspend fun getDocument(uri: String): DocumentResponse { return suspendCancellableCoroutine> { cont !-> } } Bridge Callbacks to Coroutines

Slide 60

Slide 60 text

suspend fun getDocument(uri: String): DocumentResponse { return suspendCancellableCoroutine> { cont !-> } } Bridge Callbacks to Coroutines How do we resume on success and failure?

Slide 61

Slide 61 text

suspend fun getDocument(uri: String): DocumentResponse { return suspendCancellableCoroutine> { cont !-> val call = vimeoClient.getDocument(uri, object :VimeoCallback() { override fun success(document: Document) { cont.resume(Success(document)) } override fun failure(error: VimeoError) { cont.resume(Error(error)) } }) } Bridge Callbacks to Coroutines

Slide 62

Slide 62 text

Problems • Boiler plate code • Repetitive

Slide 63

Slide 63 text

Factory Callback Function Suspend Function Improvement

Slide 64

Slide 64 text

Creating a Factory class VimeoClient { Call getDocument(String uri, VimeoCallback callback) Call comment(String uri, String comment, String password, VimeoCallback callback) }

Slide 65

Slide 65 text

Creating a Factory interface SuspendFunctionFactory { fun convertToSuspendFunction ( fn: (A, VimeoCallback) !-> Call ): suspend (A)!-> Result }

Slide 66

Slide 66 text

Creating a Factory fun convertToSuspendFunction ( fn: (A, VimeoCallback) !-> Call ): suspend (A)!-> Result = { a !-> } Function Reference

Slide 67

Slide 67 text

Creating a Factory fun convertToSuspendFunction ( fn: (A, VimeoCallback) !-> Call ): suspend (A)!-> Result = { a !-> } Suspending Function

Slide 68

Slide 68 text

Creating a Factory fun convertToSuspendFunction ( fn: (A, VimeoCallback) !-> Call ): suspend (A)!-> Result = { a !-> suspendCancellableCoroutine { cont !-> val call = fn(a, object : VimeoCallback() { override fun success(t: T) { cont.resume(Success(t)) } override fun failure(error: VimeoError) { cont.resume(Error(error)) } }) }
 }

Slide 69

Slide 69 text

Factory Callback Function Suspend Function Reusable Factory

Slide 70

Slide 70 text

Updating Model class DocumentModel( val vimeoClient: VimeoClient, val factory: SuspendFunctionFactory ) { suspend fun getDocument(): Result = }

Slide 71

Slide 71 text

Updating Model class DocumentModel(
 val vimeoClient: VimeoClient, val factory: SuspendFunctionFactory, val requestDispatcher: CoroutineDispatcher = Dispatchers.IO ) { suspend fun getDocument(): Result = 
 
 } }

Slide 72

Slide 72 text

Updating Model class DocumentModel(
 val vimeoClient: VimeoClient, val factory: SuspendFunctionFactory, val requestDispatcher: CoroutineDispatcher = Dispatchers.IO ) { suspend fun getDocument() = withContext(requestDispatcher) {
 
 } }

Slide 73

Slide 73 text

Updating Model suspend fun getDocument() = withContext(requestDispatcher) {
 result = factory.convertToSuspendFunction(vimeoClient!::getDocument)(uri) return when (result) { is VimeoApiResponse.Success !-> result.data.html.let { Success(it) } is VimeoApiResponse.Failure !-> Error() 
 } }

Slide 74

Slide 74 text

Updating Model 1. Bridge callback to coroutines in the app
 2. Update SDK to use Coroutines with Retrofit • Suspend function • Deferred type

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

repositories { maven { url 'https:!//oss.sonatype.org/content/repositories/snapshots/' } google() jcenter() } Using Coroutines with Retrofit build.gradle

Slide 77

Slide 77 text

!// Retrofit version 2.6.0 SNAPSHOT implementation 'com.squareup.retrofit2:retrofit:2.6.0-SNAPSHOT' implementation ‘com.squareup.retrofit2:converter-moshi:2.6.0-SNAPSHOT' !// Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.0' Using Coroutines with Retrofit build.gradle

Slide 78

Slide 78 text

interface VimeoService { @GET fun getDocument(@Url uri: String): Call … } Using Coroutines with Retrofit

Slide 79

Slide 79 text

interface VimeoService { @GET fun getDocument(@Url uri: String): Call … } Using Coroutines with Retrofit

Slide 80

Slide 80 text

interface VimeoService { @GET fun getDocument(@Url uri: String): Document … } Using Coroutines with Retrofit

Slide 81

Slide 81 text

interface VimeoService { @GET suspend getDocument(@Url uri: String): Call … } Using Coroutines with Retrofit

Slide 82

Slide 82 text

Using Coroutines with Retrofit class VimeoClient { suspend fun getDocument(uri: String): Document … }

Slide 83

Slide 83 text

Updating Model 1. Bridge callback to coroutines in the app
 2. Update SDK to use Coroutines with Retrofit • Suspend function • Deferred type

Slide 84

Slide 84 text

Using Deferred Type • Kotlin’s future type • It has a associated type

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

Using Deferred Type val retrofit = Retrofit.Builder() .baseUrl(baseUrl) .addCallAdapterFactory(CoroutineCallAdapterFactory()) .build()

Slide 87

Slide 87 text

Using Deferred Type interface VimeoService { @GET fun getDocument(@Url uri: String): Deferred … }

Slide 88

Slide 88 text

Using Deferred Type val vimeoClient = VimeoClient() val deferred: Deferred = vimeoClient.getDocument(uri) val document = deferred.await()

Slide 89

Slide 89 text

Update Presenter View Presenter Model

Slide 90

Slide 90 text

Update Presenter Approach • Create custom scope in presenter • Launch it on the main thread • Do context switch to make request

Slide 91

Slide 91 text

Inject Dispatcher class DocumentPresenter( val documentModel: DocumentContract.Model, val uiDispatcher: CoroutineDispatcher ) { }

Slide 92

Slide 92 text

Create scope class DocumentPresenter( val documentModel: DocumentContract.Model, val uiDispatcher: CoroutineDispatcher ) { var uiScope: CoroutineScope = CoroutineScope(uiDispatcher) }

Slide 93

Slide 93 text

Update Presenter class DocumentPresenter( val documentModel: DocumentContract.Model, val uiDispatcher: CoroutineDispatcher, ) { var uiScope: CoroutineScope = CoroutineScope(uiDispatcher) var fetchDocumentJob: Job? = null override fun onViewAttached(view: DocumentContract.View) { documentView = view fetchHtmlDocument() } }

Slide 94

Slide 94 text

Launch Coroutine fun fetchHtmlDocument() { fetchDocumentJob = uiScope.launch { } }

Slide 95

Slide 95 text

Launch Coroutine fun fetchHtmlDocument() { fetchDocumentJob = uiScope.launch { val result: Result = documentModel.getDocument() when (result) { is DocumentRequestResult.Success !-> 
 documentView!?.showRawHtml(result.document) is DocumentRequestResult.DocumentRequestError !-> { documentView!?.showGenericErrorState() } } }

Slide 96

Slide 96 text

Cancellation class DocumentPresenter( val documentModel: DocumentContract.Model, val uiDispatcher: CoroutineDispatcher, ) { … override fun onViewDetached() { uiScope.coroutineContext[Job]!?.cancel() } }

Slide 97

Slide 97 text

MVVM - Arch Comp Activity 
 
 ViewModel LiveData Repository

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

Coroutine Extensions val ViewModel.viewModelScope: CoroutineScope get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope !!= null) { return scope } return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(Job() + Dispatchers.Main)) }

Slide 100

Slide 100 text

View Model class DocumentModel(): ViewModel() { fun fetchHtmlDocument() { viewModelScope.launch { val result = documentModel.fetchHtmlDocument() } } }

Slide 101

Slide 101 text

Resources

Slide 102

Slide 102 text

https://bit.ly/2GrDMDG

Slide 103

Slide 103 text

https://speakerdeck.com/heyitsmohit/dissecting-coroutines

Slide 104

Slide 104 text

No content

Slide 105

Slide 105 text

No content

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

Resources • https://medium.com/vimeo-engineering-blog/kotlin-coroutines- vimeo-a-case-study-part-1-555e78aee8b9 • https://www.capitalone.com/tech/software-engineering/ coroutines-and-rxjava-asynchronous-programming • https://medium.com/androiddevelopers/rxjava-to-kotlin- coroutines-1204c896a700

Slide 108

Slide 108 text

Thank you! twitter.com/heyitsmohit