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

Coroutines and Rx: A battle towards Asynchrony

Amanjeet Singh
November 26, 2019
130

Coroutines and Rx: A battle towards Asynchrony

This was given at Droidcon SF 2019

Amanjeet Singh

November 26, 2019
Tweet

Transcript

  1. Coroutines and Rx: A Battle Towards Asynchrony @droid_singh ⚔

  2. Reactive Extensions “Push Based Updates”

  3. showsApi.getShowByName("Friends") .flatmap { showsApi.getCharacters(it.id) } .subscribe( { /* do something

    with characters*/ }, { /* handle errors retrieving characters*/} )
  4. showsApi.getShowByName("Friends") .flatmap { showsApi.getCharacters(it.id) } .subscribe( { /* do something

    with characters*/ }, { /* handle errors retrieving characters*/} )
  5. showsApi.getShowByName("Friends") .flatmap { showsApi.getCharacters(it.id) } .subscribe( { /* do something

    with characters*/ }, { /* handle errors retrieving characters*/} ) -Ben Christensen
  6. Coroutines “Non blocking subroutines”

  7. CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){

    // do something with characters on UI } }
  8. I O CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters =

    showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } }
  9. I O 0 sm.item = "Friends" sm.label = 1 CoroutineScope.launch(Dispatchers.IO){

    val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } }
  10. I O 0 sm.item = "Friends" sm.label = 1 showsApi.getShowsByName(“Friends”,

    cont) Continuation CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } }
  11. I O 0 1 show = sm.result sm.label = 2

    sm.item = "Friends" sm.label = 1 showsApi.getShowsByName(“Friends”, cont) CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } }
  12. I O 0 1 show = sm.result sm.label = 2

    showsApi.getCharacters(show.id, cont) sm.item = "Friends" sm.label = 1 showsApi.getShowsByName(“Friends”, cont) Continuation CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } }
  13. I O 0 1 show = sm.result sm.label = 2

    showsApi.getCharacters(show.id, cont) sm.item = "Friends" sm.label = 1 showsApi.getShowsByName(“Friends”, cont) Continuation 2 characters = sm.result CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } }
  14. ⚔ ASYNCHRONICITY

  15. ! ! ! “Do not walk away from complexity, Run”

    - Venkat Subramaniam
  16. Round 1: The Initial Combat ⚔

  17. HTTP over Retrofit.Builder() .baseUrl(MoviesApiInterface.MOVIES_ENDPOINT) .addConverterFactory(gsonConverterFactory) .addCallAdapterFactory(rxJavaCallAdapterFactory) .client(okHttp) .build() .create(MoviesApiInterface::class.java)

  18. moviesApi.getLatestMovies( ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { /* do something with

    movies*/ }, { /* handle errors retrieving movies*/} ) HTTP over
  19. Retrofit.Builder() .baseUrl(MoviesApiInterface.MOVIES_ENDPOINT) .addConverterFactory(gsonConverterFactory) .client(okHttp) .build() .create(MoviesApiInterface::class.java) HTTP over

  20. No Change HTTP over Retrofit.Builder() .baseUrl(MoviesApiInterface.MOVIES_ENDPOINT) .addConverterFactory(gsonConverterFactory) .client(okHttp) .build() .create(MoviesApiInterface::class.java)

  21. CoroutineScope.launch{ val data = moviesApiInterface.getInTheatreMovies() withContext(Dispatchers.Main){ // do something with

    data } } HTTP over
  22. Operators Learning

  23. Operators Learning

  24. ~11k ~3k https://github.com/KeepSafe/dexcount-gradle-plugin

  25. Complexity Most Most

  26. Round 2: Let’s catch that ⚔

  27. Error Propagation over 1 2 3 5 N N /

    N - 4 4
  28. Error Propagation over 1 2 3 5 N N /

    N - 4 -1/3 -1 -3 Destructive 4
  29. Error Handling • Wrap your response in Result class sealed

    class Result<out T> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>() } • Gotta catch them all onError onErrorReturn onErrorResumeNext doOnError onErrorComplete onErrorReturnItem
  30. Error Handling fun getMovies(): Observable<Result<Response>> { return moviesApi.getMovies() .map {

    Result.Success(it) } .onErrorReturn { Result.Error(it) } }
  31. Error Handling fun getMovies(): Observable<Result<Response>> { return moviesApi.getMovies() .map {

    Result.Success(it) } .onErrorReturn { Result.Error(it) } } Result
  32. Error Propagation over

  33. Error Propagation over Parent-Child Relationship

  34. Error Propagation over Parent-Child Relationship Exceptions
 Bubbling
 Tendency

  35. Error Propagation over

  36. “Exceptions are like virus for Coroutines” Error Propagation over

  37. Error Handling over • Depends on what Coroutine Builder you

    are using: Launch/Async? • Scope Intelligently • Tools for handling ☠ CoroutineExceptionHandler try/catch
  38. Error Handling over private val context: CoroutineContext = Job() +

    Dispatchers.IO val scope = CoroutineScope(context) suspend fun longRunningTask(): Unit { delay(100) throw IllegalArgumentException() } • Setup
  39. Error Handling over • launch CoroutineBuilder scope.launch{ launch{ longRunningTask() }

    }
  40. Error Handling over • Install CoroutineExceptionHandler private val ceh =

    CoroutineExceptionHandler { _, e -> println("CEH Handled Crash [$e]") } scope.launch(ceh){ launch{ longRunningTask() } }
  41. Error Handling over • Async CoroutineBuilder scope.launch{ async{ longRunningTask() }.await()

    }
  42. Error Handling over • Install try/catch scope.launch(ceh){ try{ async{ longRunningTask()

    }.await() } catch(e: Throwable){ println("Caught") } }
  43. suspend fun <T : Any> apiCall(call: suspend () -> Result<T>,

    errorMessage: String): Result<T> { return try { call() } catch (e: Exception) { Result.Error(e) } } Let’s be a pro
  44. suspend fun <T : Any> apiCall(call: suspend () -> Result<T>,

    errorMessage: String): Result<T> { return try { call() } catch (e: Exception) { Result.Error(e) } } Result Let’s be a pro
  45. suspend fun getLatestMovies(): Result<MovieResults> { return apiCall( { val data

    = moviesApiInterface.getInTheatreMovies() return@apiCall Result.Success(data.body()) }, "Couldn't get" ) } So, Now are we pro? Let’s be a pro
  46. No Let’s be a pro So, Now are we pro?

    suspend fun getLatestMovies(): Result<MovieResults> { return apiCall( { val data = moviesApiInterface.getInTheatreMovies() return@apiCall Result.Success(data.body()) }, "Couldn't get" ) }
  47. Complexity Most Most

  48. Round 3: Let’s zip them ⚔

  49. Zip Calls in Observable.zip(getPopularMovies(), getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies,

    topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io())
  50. Zip Calls in Let’s apply TEST rule Observable.zip(getPopularMovies(), getNowPlayingMovies(), BiFunction<Movies,

    Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io())
  51. Zip Calls in Threading Exception Handling Scalability Transforming results Observable.zip(getPopularMovies(),

    getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io())
  52. Zip Calls in Threading Exception Handling Scalability Transforming results Observable.zip(getPopularMovies(),

    getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io())
  53. Zip Calls in Threading Exception Handling Scalability Transforming results Observable.zip(getPopularMovies(),

    getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io())
  54. Zip Calls in Threading Exception Handling Scalability Transforming results Observable.zip(getPopularMovies(),

    getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io())
  55. Zip Calls in • Let’s cook zip +

  56. suspend fun <T1, T2, R> zip(source1: Deferred<T1>, source2: Deferred<T2>, zipper:

    (T1, T2) -> R) = coroutineScope { // wait for two calls to complete async { zipper(source1.await(), source2.await()) } } Zip Calls in • Let’s cook zip +
  57. suspend fun <T1, T2, R> zip(source1: Deferred<T1>, source2: Deferred<T2>, zipper:

    (T1, T2) -> R) = coroutineScope { // wait for two calls to complete async { zipper(source1.await(), source2.await()) } } Zip Calls in • Let’s cook zip + Zip Deferred 1 Deferred 2 Zipper Result
  58. suspend fun <T1, T2, R> zip(source1: Deferred<T1>, source2: Deferred<T2>, zipper:

    (T1, T2) -> R) = coroutineScope { // wait for two calls to complete async { zipper(source1.await(), source2.await()) } } Zip Calls in • Let’s cook zip +
  59. Zip Calls in coroutineScope.launch { val popularMovies = async{ getPopularMovies()

    } val topRatedMovies = async{ getTopRatedMovies() } zip(popularMovies, topRatedMovies) { popularResults, topRatedResults -> // Filter from popular and top rated movies return filteredResults }.await() } Threading Exception Handling Scalability Transforming results
  60. Threading Exception Handling Scalability Transforming results coroutineScope.launch { val popularMovies

    = async{ getPopularMovies() } val topRatedMovies = async{ getTopRatedMovies() } zip(popularMovies, topRatedMovies) { popularResults, topRatedResults -> // Filter from popular and top rated movies return filteredResults }.await() } Zip Calls in
  61. Threading Exception Handling Scalability Transforming results coroutineScope.launch { val popularMovies

    = async{ getPopularMovies() } val topRatedMovies = async{ getTopRatedMovies() } zip(popularMovies, topRatedMovies) { popularResults, topRatedResults -> // Filter from popular and top rated movies return filteredResults }.await() } Zip Calls in
  62. Threading Exception Handling Scalability Transforming results coroutineScope.launch { val popularMovies

    = async{ getPopularMovies() } val topRatedMovies = async{ getTopRatedMovies() } zip(popularMovies, topRatedMovies) { popularResults, topRatedResults -> // Filter from popular and top rated movies return filteredResults }.await() } Zip Calls in
  63. Complexity Most Most

  64. Round 4: Managing States ⚔

  65. https://jakewharton.com/the-state-of-managing-state-with-rxjava/

  66. val events = searchButton.clicks() .map { SearchEvent(searchEditText.text.toString()) } Power of

    RxBindings State Management through
  67. State Management through Rich with operators, Adheres to R&FP, exceptional

    handling, 
 lifecycle management, etc. val models = events.flatMap { moviesClient.searchMovies(it.searchTerm) } .map { it.results.asResult() } .onErrorReturn { Result.Error(it.toApplicationError()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
  68. Rich with operators, Adheres to R&FP, exceptional handling, 
 lifecycle

    management, etc. val models = events.flatMap { moviesClient.searchMovies(it.searchTerm) } .map { it.results.asResult() } .onErrorReturn { Result.Error(it.toApplicationError()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) State Management through
  69. Rich with operators, Adheres to R&FP, exceptional handling, 
 lifecycle

    management, etc. val models = events.flatMap { moviesClient.searchMovies(it.searchTerm) } .map { it.results.asResult() } .onErrorReturn { Result.Error(it.toApplicationError()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) State Management through
  70. Rich with operators, Adheres to R&FP, exceptional handling, 
 lifecycle

    management, etc. val models = events.flatMap { moviesClient.searchMovies(it.searchTerm) } .map { it.results.asResult() } .onErrorReturn { Result.Error(it.toApplicationError()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) State Management through
  71. What about Coroutines?

  72. What about Coroutines? Flows

  73. • Suspension based reactive streams. • Can be used to

    represent streams State Management through
  74. • Suspension based reactive streams. • Can be used to

    represent streams State Management through
  75. • Rich with operators, Adheres to R&FP, exceptional handling, 


    lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through
  76. • Rich with operators, Adheres to R&FP, exceptional handling, 


    lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through
  77. • Rich with operators, Adheres to R&FP, exceptional handling, 


    lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through
  78. • Rich with operators, Adheres to R&FP, exceptional handling, 


    lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through
  79. • Rich with operators, Adheres to R&FP, exceptional handling, 


    lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through
  80. • Rich with operators, Adheres to R&FP, exceptional handling, 


    lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } UI Bindings? State Management through
  81. Conclusion

  82. Conclusion

  83. Thank you @droid_singh @amanjeetsingh150

  84. References • https://medium.com/the-kotlin-chronicle/coroutine-exceptions-3378f51a7d33 • https://medium.com/@elizarov/ • https://speakerdeck.com/krlrozov/kotlin-coroutines-flow-is-coming • https://speakerdeck.com/ragdroid/flowing-things-not-so-strange-in-the-mvi-world •

    https://github.com/amanjeetsingh150/CoroutinesRx • https://medium.com/capital-one-tech/coroutines-and-rxjava-an-asynchronicity-comparison-part-1-asynchronous- programming-e726a925342a • Kotlin Coroutines Slack Channel • https://github.com/android/plaid