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

Coroutines and Rx: A battle towards Asynchrony

Amanjeet Singh
November 26, 2019
150

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

    View full-size slide

  2. Reactive Extensions
    “Push Based Updates”

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. Coroutines
    “Non blocking subroutines”

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide


  14. ASYNCHRONICITY

    View full-size slide

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

    View full-size slide

  16. Round 1:
    The Initial Combat

    View full-size slide

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

    View full-size slide

  18. moviesApi.getLatestMovies( )
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
    { /* do something with movies*/ },
    { /* handle errors retrieving movies*/}
    )
    HTTP over

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  22. Operators
    Learning

    View full-size slide

  23. Operators
    Learning

    View full-size slide

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

    View full-size slide

  25. Complexity
    Most
    Most

    View full-size slide

  26. Round 2:
    Let’s catch that

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  29. Error Handling
    • Wrap your response in Result class
    sealed class Result {
    data class Success(val data: T) : Result()
    data class Error(val exception: Exception) : Result()
    }
    • Gotta catch them all
    onError
    onErrorReturn onErrorResumeNext
    doOnError onErrorComplete
    onErrorReturnItem

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  32. Error Propagation over

    View full-size slide

  33. Error Propagation over
    Parent-Child Relationship

    View full-size slide

  34. Error Propagation over
    Parent-Child Relationship
    Exceptions

    Bubbling

    Tendency

    View full-size slide

  35. Error Propagation over

    View full-size slide

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

    View full-size slide

  37. Error Handling over
    • Depends on what Coroutine Builder you are using: Launch/Async?
    • Scope Intelligently

    • Tools for handling

    CoroutineExceptionHandler
    try/catch

    View full-size slide

  38. Error Handling over
    private val context: CoroutineContext = Job() + Dispatchers.IO
    val scope = CoroutineScope(context)
    suspend fun longRunningTask(): Unit {
    delay(100)
    throw IllegalArgumentException()
    }
    • Setup

    View full-size slide

  39. Error Handling over
    • launch CoroutineBuilder
    scope.launch{
    launch{
    longRunningTask()
    }
    }

    View full-size slide

  40. Error Handling over
    • Install CoroutineExceptionHandler
    private val ceh = CoroutineExceptionHandler { _, e ->
    println("CEH Handled Crash [$e]")
    } scope.launch(ceh){
    launch{
    longRunningTask()
    }
    }

    View full-size slide

  41. Error Handling over
    • Async CoroutineBuilder
    scope.launch{
    async{
    longRunningTask()
    }.await()
    }

    View full-size slide

  42. Error Handling over
    • Install try/catch
    scope.launch(ceh){
    try{
    async{
    longRunningTask()
    }.await()
    } catch(e: Throwable){
    println("Caught")
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  45. suspend fun getLatestMovies(): Result {
    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

    View full-size slide


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

    View full-size slide

  47. Complexity
    Most
    Most

    View full-size slide

  48. Round 3:
    Let’s zip them

    View full-size slide

  49. Zip Calls in
    Observable.zip(getPopularMovies(), getNowPlayingMovies(),
    BiFunction { popularMovies, topRatedMovies, ->
    // Filter from popular and top rated movies
    }.subscribeOn(Scheduler.io())

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  55. Zip Calls in
    • Let’s cook zip
    +

    View full-size slide

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

    View full-size slide

  57. suspend fun zip(source1: Deferred, source2: Deferred,
    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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  63. Complexity
    Most
    Most

    View full-size slide

  64. Round 4:
    Managing States

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  71. What about Coroutines?

    View full-size slide

  72. What about Coroutines?

    Flows

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  81. Thank you
    @droid_singh @amanjeetsingh150

    View full-size slide

  82. 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

    View full-size slide