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

Coroutines and Rx: A battle towards Asynchronicity

Coroutines and Rx: A battle towards Asynchronicity

Amanjeet Singh

August 31, 2019
Tweet

More Decks by Amanjeet Singh

Other Decks in Programming

Transcript

  1. Coroutines and Rx: A Battle Towards
    Asynchronicity
    @droid_singh

    View Slide

  2. Reactive Extensions
    “Push Based Updates”

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. Coroutines
    “Non blocking subroutines”

    View 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 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 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 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 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 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 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 Slide


  14. ASYNCHRONICITY

    View Slide

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

    View Slide

  16. Round 1:
    The Initial Combat

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. Operators
    Learning

    View Slide

  23. Operators
    Learning

    View Slide

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

    View Slide

  25. Complexity
    Most
    Most

    View Slide

  26. Round 2:
    Let’s catch that

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

  32. Error Propagation over

    View Slide

  33. Error Propagation over
    Parent-Child Relationship

    View Slide

  34. Error Propagation over
    Parent-Child Relationship
    Exceptions

    Bubbling

    Tendency

    View Slide

  35. Error Propagation over

    View Slide

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

    View Slide

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

    • Tools for handling

    CoroutineExceptionHandler
    try/catch

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 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 Slide

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

    View Slide

  46. suspend fun getLatestMovies(): Result {
    return apiCall(
    {
    val data = moviesApiInterface.getInTheatreMoviesAsync().await()
    return@apiCall Result.Success(data.body())
    },
    "Couldn't get"
    )
    }

    So, Now are we pro?
    No
    Let’s be a pro

    View Slide

  47. Complexity
    Most
    Most

    View Slide

  48. Round 3:
    Let’s zip them

    View Slide

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

    View 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 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 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 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 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

  60. Zip Calls in
    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()
    }

    View Slide

  61. Zip Calls in
    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()
    }

    View Slide

  62. Zip Calls in
    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()
    }

    View Slide

  63. Complexity
    Most
    Most

    View Slide

  64. Round 4:
    Managing States

    View Slide

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

    View Slide

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

    View 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 Slide

  68. 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 Slide

  69. 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 Slide

  70. 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 Slide

  71. What about Coroutines?

    View Slide

  72. What about Coroutines?

    Flows

    View Slide

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

    View Slide

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

    View Slide

  75. State Management through
    • 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)
    }
    }

    View Slide

  76. State Management through
    • 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)
    }
    }

    View Slide

  77. State Management through
    • 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)
    }
    }

    View Slide

  78. State Management through
    • 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)
    }
    }

    View Slide

  79. State Management through
    • 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)
    }
    }

    View Slide

  80. State Management through
    • 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?

    View Slide

  81. Conclusion

    View Slide

  82. Conclusion

    View Slide

  83. Thank you
    @droid_singh @amanjeetsingh150

    View Slide

  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

    View Slide