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

Fueled Reactive apps with Asynchronous Flow

Fueled Reactive apps with Asynchronous Flow

Reactive Extensions are widely used on large scale successful Android applications, and for this, the most popular library is an adaptation of these Reactive Extensions, the very well known RxJava.
What if we could use the existing Kotlin suspending functions asynchronously to return more than a single value?
How could we return multiple asynchronously backed results in Kotlin?
Kotlin Flows to the rescue!
Would Kotlin Flows replace RxJava in the long term on the Android platform?
How could we start migrating smoothly to Flow on hybrid (Java & Kotlin) languages apps?
These slides have been used during #VirtualAndroidMakers @AndroidMakersFR

Raul Hernandez Lopez

April 20, 2020
Tweet

More Decks by Raul Hernandez Lopez

Other Decks in Programming

Transcript

  1. Fueled Reactive apps with
    Asynchronous Flow
    @raulhernandezl

    View full-size slide

  2. Software Engineer Raul Hernandez Lopez
    @ Twitter
    raulh82vlc
    @raulhernandezl

    View full-size slide

  3. Agenda
    @raulhernandezl

    View full-size slide

  4. 1. Use case
    2. Migration strategy
    3. Asynchronous communication: Streams
    4. Basics
    5. Implementation
    6. Demo time
    7. Lessons learnt
    8. Next steps
    @raulhernandezl
    AGENDA

    View full-size slide

  5. Use case
    @raulhernandezl

    View full-size slide

  6. Tweets Search
    (old) sample app
    @raulhernandezl

    View full-size slide

  7. Tweets Search
    (old) sample app
    Search for Tweets
    with the #hashtag
    @raulhernandezl

    View full-size slide

  8. Participation needed!
    @raulhernandezl

    View full-size slide

  9. Let’s all tweet #tweetswithflow
    Participation needed!
    @raulhernandezl

    View full-size slide

  10. Participation needed!
    Let’s all tweet #tweetswithflow
    Add the hashtag at the first 140 characters
    @raulhernandezl

    View full-size slide

  11. Search for the
    #hashtag
    @raulhernandezl
    #tweetswithflow

    View full-size slide

  12. Migration Strategy
    @raulhernandezl

    View full-size slide

  13. Legacy means Refactoring
    @raulhernandezl

    View full-size slide

  14. Steps to follow
    @raulhernandezl

    View full-size slide

  15. Analysing previous Architecture
    @raulhernandezl

    View full-size slide

  16. Repository
    Network data
    source
    DB data source
    Data Layer
    @raulhernandezl

    View full-size slide

  17. Use Case Repository
    Network data
    source
    DB data source
    Business Layer
    @raulhernandezl

    View full-size slide

  18. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    Presentation Layer
    @raulhernandezl

    View full-size slide

  19. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    Model View Presenter (MVP) + Clean Architecture
    @raulhernandezl

    View full-size slide

  20. Check backwards compatibility
    requirements
    @raulhernandezl

    View full-size slide

  21. @raulhernandezl

    View full-size slide

  22. Old & New need to co-exist together by the
    time being
    @raulhernandezl

    View full-size slide

  23. Analysing pinpoints &
    connections
    @raulhernandezl

    View full-size slide

  24. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    requests
    View Delegate
    View
    Listener
    JAVA + RXJAVA EVERYWHERE
    @raulhernandezl
    executes
    starts
    injects

    View full-size slide

  25. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    MIGRATION of DATASOURCES: KOTLIN + RXJAVA
    @raulhernandezl
    starts
    injects

    View full-size slide

  26. Presenter Use Case Repository
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    MIGRATION of DATASOURCES: KOTLIN + RXJAVA + FLOW
    @raulhernandezl
    starts
    injects

    View full-size slide

  27. Presenter Use Case Repository
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    MIGRATION of REPOSITORY: KOTLIN + RXJAVA
    @raulhernandezl
    starts
    injects

    View full-size slide

  28. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    MIGRATION of REPOSITORY: KOTLIN + RXJAVA + FLOW
    @raulhernandezl
    starts
    injects

    View full-size slide

  29. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    MIGRATION of REPOSITORY: KOTLIN + FLOW / SUSPEND
    @raulhernandezl
    starts
    injects

    View full-size slide

  30. Presenter Use Case Repository
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    MIGRATION of USE CASE: KOTLIN + FLOW / SUSPEND
    @raulhernandezl
    starts
    injects

    View full-size slide

  31. Presenter Use Case Repository
    Network data
    source
    DB data source
    requests
    executes
    Flows
    Flows
    Flows
    View /
    Callbacks
    View Delegate
    View
    Listener
    @raulhernandezl
    WHERE USING FLOWS?
    starts
    injects

    View full-size slide

  32. Presenter Use Case Repository
    Network data
    source
    DB data source
    requests
    executes
    Flows
    Flows
    Flows
    View /
    Callbacks
    View Delegate
    View
    Listener
    @raulhernandezl
    CAN WE CHANGE VIEWDELEGATE’S RXJAVA?
    starts
    injects

    View full-size slide

  33. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    View Delegate
    starts
    executes requests
    View
    Listener
    Flows
    Channels
    as Flows
    injects
    VIEWDELEGATE: CHANNELS
    @raulhernandezl
    Flows
    Flows

    View full-size slide

  34. Benefits
    @raulhernandezl

    View full-size slide

  35. Re-usage of existent components
    @raulhernandezl

    View full-size slide

  36. Layers collaboration
    @raulhernandezl

    View full-size slide

  37. Asynchronous
    communication:
    Streams
    @raulhernandezl

    View full-size slide

  38. Conversations like Streams
    @raulhernandezl

    View full-size slide

  39. Conversation / Start request
    Raul
    @raulhernandezl

    View full-size slide

  40. Conversations / Acknowledge received
    Cristina
    ACK
    @raulhernandezl

    View full-size slide

  41. Conversations / Starts stream
    Cristina
    ACK
    @raulhernandezl

    View full-size slide

  42. Conversations / Stream received
    Raul Cristina
    ACK
    @raulhernandezl

    View full-size slide

  43. Conversations like Streams
    Raul Cristina
    ACK 1..n ACK 1..n
    @raulhernandezl

    View full-size slide

  44. Basics
    @raulhernandezl

    View full-size slide

  45. One shot operations
    @raulhernandezl

    View full-size slide

  46. ONE SHOT OPERATIONS
    SINGLE / MAYBE / COMPLETABLE
    @raulhernandezl

    View full-size slide

  47. ONE SHOT OPERATIONS
    SINGLE / MAYBE / COMPLETABLE
    SUSPEND FUNCTION
    @raulhernandezl

    View full-size slide

  48. ONE SHOT OPERATIONS
    @raulhernandezl
    SINGLE Single
    SUSPEND FUNCTION
    OBJECT
    suspend () -> T

    View full-size slide

  49. ONE SHOT OPERATIONS
    @raulhernandezl
    SINGLE Single
    SUSPEND FUNCTION
    OBJECT
    suspend () -> T
    MAYBE Maybe
    SUSPEND FUNCTION
    NULLABLE
    suspend () -> T?

    View full-size slide

  50. ONE SHOT OPERATIONS
    @raulhernandezl
    SINGLE Single
    SUSPEND FUNCTION
    OBJECT
    suspend () -> T
    MAYBE Maybe
    SUSPEND FUNCTION
    NULLABLE
    suspend () -> T?
    COMPLETABLE Completable
    SUSPEND FUNCTION
    UNIT
    suspend () -> Unit

    View full-size slide

  51. Streams: Hot & Cold
    @raulhernandezl

    View full-size slide

  52. STREAMS: COLD
    OBSERVABLE / FLOWABLE
    @raulhernandezl

    View full-size slide

  53. STREAMS: COLD
    OBSERVABLE / FLOWABLE
    FLOW
    @raulhernandezl

    View full-size slide

  54. STREAMS: HOT
    SUBJECT
    @raulhernandezl

    View full-size slide

  55. STREAMS: HOT
    SUBJECT
    CHANNELS
    @raulhernandezl

    View full-size slide

  56. Threading
    @raulhernandezl

    View full-size slide

  57. THREADING
    SCHEDULER
    @raulhernandezl

    View full-size slide

  58. THREADING
    SCHEDULER
    DISPATCHER
    @raulhernandezl

    View full-size slide

  59. Lifecycle
    @raulhernandezl

    View full-size slide

  60. LIFECYCLE
    DISPOSABLE
    @raulhernandezl

    View full-size slide

  61. LIFECYCLE
    DISPOSABLE
    SCOPE
    @raulhernandezl

    View full-size slide

  62. SCOPE
    STRUCTURED CONCURRENCY
    @raulhernandezl

    View full-size slide

  63. STRUCTURED CONCURRENCY
    RECURSIVE CLEAN UP
    @raulhernandezl

    View full-size slide

  64. SCOPE
    PROCESS 3
    PROCESS 2
    CANCELLATION
    PROCESS 1
    @raulhernandezl

    View full-size slide

  65. SCOPE
    PROCESS 3
    PROCESS 2
    CANCELLATION
    PROCESS 1
    @raulhernandezl

    View full-size slide

  66. CANCELLATION
    RECURSIVE CLEAN UP
    AVOIDS MEMORY LEAKS
    @raulhernandezl

    View full-size slide

  67. AVOID SIDE-EFFECTS ON CANCELLATION
    SCOPE + SupervisorJob
    @raulhernandezl

    View full-size slide

  68. AVOID SIDE-EFFECTS ON CANCELLATION
    SCOPE + SupervisorJob
    AVOID OTHER CHILDREN
    CANCELLATION
    @raulhernandezl

    View full-size slide

  69. SCOPE
    +
    SupervisorJob
    PROCESS 3
    PROCESS 2
    STRUCTURED CONCURRENCY CANCELLATION
    PROCESS 1
    @raulhernandezl

    View full-size slide

  70. STRUCTURED CONCURRENCY
    ERROR PROPAGATION
    @raulhernandezl

    View full-size slide

  71. STRUCTURED CONCURRENCY
    ERROR PROPAGATION
    CATCH / HANDLE THEM
    @raulhernandezl

    View full-size slide

  72. Implementation
    @raulhernandezl

    View full-size slide

  73. build.gradle
    ...
    // Coroutines
    implementation
    "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
    implementation
    "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
    coroutines_version = '1.3.2'
    Gradle dependencies
    @raulhernandezl

    View full-size slide

  74. build.gradle
    ...
    // Coroutines
    implementation
    "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
    implementation
    "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
    coroutines_version = '1.3.2'
    // >= 1.3
    Gradle dependencies
    @raulhernandezl

    View full-size slide

  75. Data Sources Design:
    Network & DB
    @raulhernandezl

    View full-size slide

  76. Presenter Use Case Repository
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    FLOWS to OBSERVABLES
    @raulhernandezl

    View full-size slide

  77. @Singleton
    class NetworkDataSourceImpl @Inject constructor(
    private val twitterApi: TwitterApi,
    private val connectionHandler: ConnectionHandler,
    private val requestsIOHandler: RequestsIOHandler,
    private val taskThreading: TaskThreading
    ) : NetworkDataSource
    NetworkDataSourceImpl constructor dependencies
    @raulhernandezl

    View full-size slide

  78. class TaskThreadingImpl @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    }
    TaskThreading and threads
    @raulhernandezl

    View full-size slide

  79. class TaskThreadingImpl @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    override fun ui(): Scheduler = mainScheduler
    override fun io(): Scheduler = ioScheduler
    override fun computation(): Scheduler = computationScheduler
    }
    TaskThreading and Schedulers
    @raulhernandezl

    View full-size slide

  80. Single> search(String token, String query);
    Case 1) NetworkDataSource with Single
    @raulhernandezl

    View full-size slide

  81. public Single> search(String token, String query) {
    return twitterApi.search(
    requestsIOHandler.getTokenFormatted(access.getToken()),
    query)
    .subscribeOn(taskThreading.io())
    .observeOn(taskThreading.computation())
    .filter(requestsIOHandler::searchHasNoErrorResponse)
    .map(requestsIOHandler::getSearchContent)
    .map(requestsIOHandler::getTweets)
    .flatMapSingle(Single::just));
    }
    Case 1) NetworkDataSource w/ Single
    @raulhernandezl

    View full-size slide

  82. public Single> search(String token, String query) {
    return twitterApi.search(
    requestsIOHandler.getTokenFormatted(access.getToken()),
    query)
    .subscribeOn(taskThreading.io())
    .observeOn(taskThreading.computation())
    .filter(requestsIOHandler::searchHasNoErrorResponse)
    .map(requestsIOHandler::getSearchContent)
    .map(requestsIOHandler::getTweets)
    .flatMapSingle(Single::just));
    Case 1) NetworkDataSource w/ Single
    @raulhernandezl

    View full-size slide

  83. fun search(token: String, query: String)
    : Single>>
    Case 1) NetworkDataSource in Kotlin
    @raulhernandezl

    View full-size slide

  84. suspend search(token: String, query: String):
    Either>
    Case 1) NetworkDataSource with suspend

    View full-size slide

  85. override suspend fun search(token: String, query: String)
    : Either> {
    ...
    }
    Case 1) NetworkDataSource with suspend
    @raulhernandezl

    View full-size slide

  86. override suspend fun search(token: String, query: String)
    : Either> {
    val response = twitterApi.search(
    requestsIOHandler.getTokenFormatted(token), query)
    return if (requestsIOHandler.searchIsSuccessful(response)) {
    val tweets = requestsIOHandler.getTweets(response)
    Either.right(tweets)
    } else {
    Either.left(EmptyResponseException(response.msg()))
    }
    }
    Case 1) NetworkDataSource with suspend
    @raulhernandezl

    View full-size slide

  87. @ExperimentalCoroutinesApi
    override fun search(token: String, query: String)
    : Flow>> =
    flow>> {
    ...
    emit(myListOfTweets)
    }
    Case 2) NetworkDataSource emit with the flow builder
    @raulhernandezl

    View full-size slide

  88. @ExperimentalCoroutinesApi
    override fun search(token: String, query: String)
    : Flow>> =
    flow>> {
    ...
    }.flowOn(taskThreading.ioDispatcher())
    flowOn operator
    @raulhernandezl

    View full-size slide

  89. class TaskThreadingImpl @Inject constructor() : TaskThreading {
    private val computationCoroutineDispatcher: CoroutineDispatcher = Default
    private val ioCoroutineDispatcher: CoroutineDispatcher = IO
    private val mainCoroutineDispatcher: CoroutineDispatcher = Main
    ...
    override fun uiDispatcher(): CoroutineDispatcher = mainCoroutineDispatcher
    override fun ioDispatcher(): CoroutineDispatcher = ioCoroutineDispatcher
    override fun computationDispatcher(): CoroutineDispatcher =
    computationCoroutineDispatcher
    }
    TaskThreading with Dispatchers
    @raulhernandezl

    View full-size slide

  90. @ExperimentalCoroutinesApi
    override fun search(token: String, query: String)
    : Flow>> =
    flow>> {
    val response = twitterApi.search(
    requestsIOHandler.getTokenFormatted(token), query)
    if (requestsIOHandler.searchIsSuccessful(response)) {
    val tweets = requestsIOHandler.getTweets(response)
    emit(Either.right(tweets))
    } else {
    emit(Either.left(EmptyResponseException(response.msg())))
    }
    }.flowOn(taskThreading.ioDispatcher())
    Case 2) NetworkDataSource emit with Flow
    @raulhernandezl

    View full-size slide

  91. fun search(token: String, query: String)
    : Observable>>
    Case 2) NetworkDataSource with Observable
    @raulhernandezl

    View full-size slide

  92. @ExperimentalCoroutinesApi
    override fun search(token: String, query: String)
    : Observable>> =
    flow>> {
    val response = twitterApi.search(
    requestsIOHandler.getTokenFormatted(token), query)
    if (requestsIOHandler.searchIsSuccessful(response)) {
    val tweets = requestsIOHandler.getTweets(response)
    emit(Either.right(tweets))
    } else {
    emit(Either.left(EmptyResponseException()))
    }
    }.flowOn(taskThreading.ioDispatcher())
    .asObservable()
    Case 2) NetworkDataSource with asObservable
    kotlinx-coroutines-rx2
    @raulhernandezl

    View full-size slide

  93. Flow.asObservable -> Converts the given flow to
    asObservable
    @raulhernandezl
    kotlinx-coroutines-rx2

    View full-size slide

  94. Flow.asObservable -> Converts the given flow to
    a cold Observable
    asObservable
    @raulhernandezl

    View full-size slide

  95. Flow.asFlowable-> Converts the given flow to
    a cold Flowable
    asFlowable
    @raulhernandezl

    View full-size slide

  96. ObservableSource.asFlow -> Converts the given cold
    ObservableSource to Flow
    asFlow
    @raulhernandezl

    View full-size slide

  97. @Query("SELECT *
    FROM ${Tweet.TABLE_NAME}
    WHERE tweet_id IN(:tweetIds) ORDER BY created_at DESC")
    fun retrieveAllTweetsForTweetsIdsRx(tweetIds: List):
    Observable>
    Database datasource (DAO)
    @raulhernandezl

    View full-size slide

  98. Repository Design
    Step 1: RxJava approach
    @raulhernandezl

    View full-size slide

  99. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    REPOSITORY: KOTLIN + FLOW + RXJAVA
    @raulhernandezl

    View full-size slide

  100. @Singleton
    class TweetsRepositoryImpl @Inject constructor(
    private val networkDataSource: NetworkDataSource,
    private val tweetsDataSource: TweetDao,
    private val mapperTweets: TweetsNetworkToDBMapperList,
    private val tokenDataSource: TokenDao,
    private val queryDataSource: QueryDao,
    private val tweetQueryJoinDataSource: TweetQueryJoinDao,
    private val mapperToken: TokenNetworkToDBMapper,
    private val taskThreading: TaskThreading
    ) : TweetsRepository {
    TweetsRepository constructor dependencies
    @raulhernandezl

    View full-size slide

  101. override fun search(token: String, query: String)
    : Single>> {
    return rxSingle {
    val response = twitterApi.search(
    requestsIOHandler.getTokenFormatted(token), query)
    if (requestsIOHandler.searchIsSuccessful(response)) {
    val tweets = requestsIOHandler.getTweets(response)
    Either.right(tweets)
    } else {
    Either.left(EmptyResponseException())
    }
    }
    }
    Case 1) NetworkDataSource with... rxSingle
    @raulhernandezl
    kotlinx-coroutines-rx2

    View full-size slide

  102. val eitherResult = networkDataSource.search(token, query).await()
    val tweets = mapperTweets.map(eitherResult)
    Case 1) TweetsRepository waiting for Single
    @raulhernandezl
    kotlinx-coroutines-rx2

    View full-size slide

  103. val eitherResult = networkDataSource.search(token, query).await()
    val tweets = mapperTweets.map(eitherResult)
    Case 1) TweetsRepository waiting for Single
    I can wait for that
    single value without
    blocking anything
    @raulhernandezl
    kotlinx-coroutines-rx2

    View full-size slide

  104. fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource.search(token, query)
    ...
    }
    Case 2) TweetsRepository: Kotlin + RxJava

    View full-size slide

  105. fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource.search(token, query)
    .map { either -> mapperTweets.map(either) }
    ...
    }
    Case 2) TweetsRepository: Kotlin + RxJava

    View full-size slide

  106. fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource.search(token, query)
    .map { either -> mapperTweets.map(either) }
    .doOnNext { tweetsToAdd ->
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(tweetsToAdd)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    convert(tweetsToAdd, query))
    ...
    }
    Case 2) TweetsRepository: Kotlin + RxJava

    View full-size slide

  107. fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource.search(token, query)
    .map { either -> mapperTweets.map(either) }
    .doOnNext { tweetsToAdd ->
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(tweetsToAdd)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    convert(tweetsToAdd, query))
    }.flatMap {
    tweetQueryJoinDataSource.retrieveAllTweetsIdRxAQuery(query)
    .flatMap { tweetIds
    -> tweetsDataSource
    .retrieveAllTweetsRxForTweetsIds(tweetIds)
    }
    }
    }
    Case 2) TweetsRepository: Kotlin + RxJava

    View full-size slide

  108. @ExperimentalCoroutinesApi
    ...
    @FlowPreview
    @InternalCoroutinesApi
    Case 2) TweetsRepository: Flow
    @raulhernandezl

    View full-size slide

  109. @ExperimentalCoroutinesApi
    ...
    @FlowPreview .debounce()
    @InternalCoroutinesApi .collect()
    Case 2) TweetsRepository: Flow
    @raulhernandezl

    View full-size slide

  110. ...
    fun getSearchTweets(token: String, query: String): Flow> {
    return networkDataSource
    .search(token, query)
    .map { either -> mapperTweets.map(either) }
    ...
    }
    Case 2) TweetsRepository: Flow
    @raulhernandezl

    View full-size slide

  111. ...
    fun getSearchTweets(token: String, query: String): Flow> {
    return networkDataSource
    .search(token, query)
    .map { either -> mapperTweets.map(either) }
    .onEach {
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(it)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    convert(it,query))
    }
    ...
    }
    Case 2) TweetsRepository: onEach
    @raulhernandezl

    View full-size slide

  112. ...
    fun getSearchTweets(token: String, query: String): Flow> {
    return networkDataSource
    .search(token, query)
    .map { either -> mapperTweets.map(either) }
    .onEach {
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(it)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    convert(it,query))
    }.flatMapConcat {
    val tweetIds =
    tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    return@flatMapConcat tweetsDataSource
    .retrieveAllTweetsForTweetsIdsFlow(tweetIds)
    }
    }
    Case 2) TweetsRepository: flatMapConcat
    @raulhernandezl

    View full-size slide

  113. ...
    fun getSearchTweets(token: String, query: String): Flow> {
    return networkDataSource
    .search(token, query)
    .map { either -> mapperTweets.map(either) }
    .onEach {
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(it)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    convert(it,query))
    }.flatMapConcat {
    val tweetIds =
    tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    return@flatMapConcat tweetsDataSource
    .retrieveAllTweetsForTweetsIdsFlow(tweetIds)
    }.flowOn(taskThreading.ioDispatcher())
    }
    Case 2) TweetsRepository: flowOn
    @raulhernandezl

    View full-size slide

  114. @Query("SELECT *
    FROM ${Tweet.TABLE_NAME}
    WHERE tweet_id IN(:tweetIds) ORDER BY created_at DESC")
    fun retrieveAllTweetsForTweetsIdsFlow(tweetIds: List):
    Flow>
    Database datasource (DAO) with Flow
    @raulhernandezl

    View full-size slide

  115. ...
    fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource
    .search(token, query)
    .map { either -> mapperTweets.map(either) }
    .onEach {
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(it)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    convert(it,query))
    }.flatMapConcat {
    val tweetIds =
    tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    return@flatMapConcat tweetsDataSource
    .retrieveAllTweetsForTweetsIdsFlow(tweetIds)
    }.flowOn(taskThreading.ioDispatcher()).asObservable()
    }
    Case 2) TweetsRepository: Flow to Observable
    @raulhernandezl
    kotlinx-coroutines-rx2

    View full-size slide

  116. Repository Design
    Step 2: Suspend approach
    @raulhernandezl

    View full-size slide

  117. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    REPOSITORY: KOTLIN + FLOW / SUSPEND
    @raulhernandezl

    View full-size slide

  118. suspend fun getSearchTweets(query: String): List {
    val accessToken = tokenDataSource.getAccessToken()
    ?: throw BadTokenException("No token was found")
    val token = accessToken.token
    ...
    }
    Case 1) TweetsRepository: Suspend
    @raulhernandezl

    View full-size slide

  119. suspend fun getSearchTweets(query: String): List {
    val accessToken = tokenDataSource.getAccessToken()
    ?: throw BadTokenException("No token was found")
    val token = accessToken.token
    val eitherResult = networkDataSource
    .search(token, query)
    val tweets = mapperTweets.map(eitherResult)
    ...
    }
    Case 1) TweetsRepository: Suspend
    @raulhernandezl

    View full-size slide

  120. suspend fun getSearchTweets(query: String): List {
    val accessToken = tokenDataSource.getAccessToken()
    ?: throw BadTokenException("No token was found")
    val token = accessToken.token
    val eitherResult = networkDataSource
    .search(token, query)
    val tweets = mapperTweets.map(eitherResult)
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(tweets)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    tweetQueryJoinDataSource.convert(tweets, query))
    ...
    }
    Case 1) TweetsRepository: Suspend
    @raulhernandezl

    View full-size slide

  121. suspend fun getSearchTweets(query: String): List {
    val accessToken = tokenDataSource.getAccessToken()
    ?: throw BadTokenException("No token was found")
    val token = accessToken.token
    val eitherResult = networkDataSource
    .search(token, query)
    val tweets = mapperTweets.map(eitherResult)
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(tweets)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    tweetQueryJoinDataSource.convert(tweets, query))
    val tweetIds = tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    return tweetsDataSource.retrieveAllTweetsForTweetsIdsFlow(tweetIds)
    }
    @raulhernandezl
    Case 1) TweetsRepository: Flow returned into suspend
    function?

    View full-size slide

  122. suspend fun getSearchTweets(query: String): List {
    val accessToken = tokenDataSource.getAccessToken()
    ?: throw BadTokenException("No token was found")
    val token = accessToken.token
    val eitherResult = networkDataSource
    .search(token, query)
    val tweets = mapperTweets.map(eitherResult)
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(tweets)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    tweetQueryJoinDataSource.convert(tweets, query))
    val tweetIds = tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    return tweetsDataSource.retrieveAllTweetsForTweetsIdsFlow(tweetIds)
    }
    Case 1) TweetsRepository: Flow returned into suspend
    function?
    @raulhernandezl
    NO!

    View full-size slide

  123. suspend fun getSearchTweets(query: String): List {
    val accessToken = tokenDataSource.getAccessToken()
    ?: throw BadTokenException("No token was found")
    val token = accessToken.token
    val eitherResult = networkDataSource
    .search(token, query)
    val tweets = mapperTweets.map(eitherResult)
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(tweets)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    tweetQueryJoinDataSource.convert(tweets, query))
    val tweetIds = tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    return tweetsDataSource.retrieveAllTweetsForTweetsIds(tweetIds)
    }
    Case 1) TweetsRepository: a suspend function is needed
    @raulhernandezl
    YES!

    View full-size slide

  124. @Query("SELECT *
    FROM ${Tweet.TABLE_NAME}
    WHERE tweet_id IN(:tweetIds) ORDER BY created_at DESC")
    suspend fun retrieveAllTweetsForTweetsIds(tweetIds: List):
    List
    Database datasource (DAO) with suspend
    @raulhernandezl

    View full-size slide

  125. @ExperimentalCoroutinesApi
    ...
    @FlowPreview
    @InternalCoroutinesApi
    Case 2) TweetsRepository: Flow
    @raulhernandezl

    View full-size slide

  126. ...
    fun getSearchTweets(query: String)
    = flow> {
    ...
    val tweetIds = tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    emitAll(tweetsDataSource.retrieveAllTweetsForTweetsIdsFlow(tweetIds))
    }.flowOn(taskThreading.ioDispatcher())
    Case 1.. 2) TweetsRepository: emitAll values from DB (Flow)
    @raulhernandezl
    YES!

    View full-size slide

  127. ...
    fun getSearchTweets(query: String)
    = flow> {
    ...
    val tweetIds = tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    emit(tweetsDataSource.retrieveAllTweetsForTweetsIds(tweetIds))
    }.flowOn(taskThreading.ioDispatcher())
    Case 1.. 2) TweetsRepository: emit a suspend List
    @raulhernandezl
    YES!

    View full-size slide

  128. ...
    fun getSearchTweets(query: String)
    = flow> {
    ...
    // retrieve old values from DB
    emit(tweetsDataSource.retrieveAllTweetsForTweetsIds(tweetIds))
    // get fresh values from network & saved them into DB
    val eitherResult = networkDataSource.search(token, query)
    ...
    // saved network into DB & emit fresh values from DB
    val tweetIds = tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query)
    emit(tweetsDataSource.retrieveAllTweetsForTweetsIds(tweetIds))
    }.flowOn(taskThreading.ioDispatcher())
    Case 1.. 2) TweetsRepository: emit more than once
    @raulhernandezl
    YES!

    View full-size slide

  129. ...
    fun getSearchTweets(query: String): Observable>
    = flow> {
    ...
    }.flowOn(taskThreading.ioDispatcher()).asObservable()
    Case 2) Repository: Flow to Observable
    @raulhernandezl
    kotlinx-coroutines-rx2

    View full-size slide

  130. Use Case Design
    @raulhernandezl

    View full-size slide

  131. Presenter Use Case Repository
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener RxSearch
    USE CASE: KOTLIN + FLOW / SUSPEND
    @raulhernandezl

    View full-size slide

  132. @ActivityScope
    class SearchTweetUseCase @Inject constructor(
    private val tweetsRepository: TweetsRepository,
    private val taskThreading: TaskThreading
    ) : UseCase {
    SearchTweetUseCase constructor dependencies
    @raulhernandezl

    View full-size slide

  133. @Override
    void execute(final String query, @Nullable SearchCallback callback) {
    this.callback = callback;
    if(callback != null) { callback.onShowLoader(); }
    repository.searchTweet(query)
    ...
    .subscribe(); // action for each stream
    }
    SearchTweetUseCase: RxJava + Java
    @raulhernandezl

    View full-size slide

  134. @Override
    void execute(final String query, @Nullable SearchCallback callback) {
    this.callback = callback;
    if(callback != null) { callback.onShowLoader(); }
    repository.searchTweet(query)
    .subscribe(
    this::onSuccess,// action for each stream
    this::onError); // error handling
    }
    SearchTweetUseCase: RxJava + Java
    @raulhernandezl

    View full-size slide

  135. @Override
    void execute(final String query, @Nullable SearchCallback callback) {
    this.callback = callback;
    if(callback != null) { callback.onShowLoader(); }
    disposable = repository.searchTweet(query)
    .subscribeOn(taskThreading.computation()) // subscribed to
    .observeOn(taskThreading.ui()) // action performed at
    .subscribe(
    this::onSuccess,// action for each stream
    this::onError); // error handling
    }
    SearchTweetUseCase: Lifecycle & threading
    @raulhernandezl

    View full-size slide

  136. @ExperimentalCoroutinesApi
    override fun execute(query: String,
    callbackInput: SearchCallback?) {
    callback = callbackInput
    callback?.onShowLoader()
    ...
    }
    SearchTweetUseCase: Kotlin
    @raulhernandezl

    View full-size slide

  137. @ExperimentalCoroutinesApi
    override fun execute(query: String,
    callbackInput: SearchCallback?) {
    callback = callbackInput
    callback?.onShowLoader()
    ...
    }
    SearchTweetUseCase: Flow + Kotlin with scope
    @raulhernandezl
    private val scope = CoroutineScope(taskThreading.uiDispatcher() +
    SupervisorJob())

    View full-size slide

  138. @ExperimentalCoroutinesApi
    override fun execute(query: String,
    callbackInput: SearchCallback?) {
    callback = callbackInput
    callback?.onShowLoader()
    scope.launch {
    repository.searchTweet(query)
    .catch { // error handling }
    .collect { // UI actions for each stream }
    }
    }
    SearchTweetUseCase: Flow + Kotlin with scope.launch
    @raulhernandezl
    private val scope = CoroutineScope(taskThreading.uiDispatcher() +
    SupervisorJob())

    View full-size slide

  139. @ExperimentalCoroutinesApi
    override fun execute(query: String,
    callbackInput: SearchCallback?) {
    callback = callbackInput
    callback?.onShowLoader()
    repository.searchTweet(query)
    .onEach { //iteration for each stream }
    .catch { // error handling }
    .launchIn(scope)
    }
    SearchTweetUseCase: Flow + Kotlin launchIn
    @raulhernandezl
    private val scope = CoroutineScope(taskThreading.uiDispatcher() +
    SupervisorJob())

    View full-size slide

  140. SearchTweetUseCase: onEach
    @ExperimentalCoroutinesApi
    override fun execute(query: String,
    callbackInput: SearchCallback?) {
    ...
    repository.searchTweet(query)
    .onEach {
    onSuccess(it)
    }
    .catch { e ->
    onError(e)
    }.launchIn(scope)
    }
    @raulhernandezl

    View full-size slide

  141. SearchTweetUseCase: catch
    @ExperimentalCoroutinesApi
    override fun execute(query: String,
    callbackInput: SearchCallback?) {
    ...
    repository.searchTweet(query)
    .onEach {
    onSuccess(it)
    }
    .catch { e ->
    onError(e)
    }.launchIn(scope)
    }
    @raulhernandezl

    View full-size slide

  142. SearchTweetUseCase: scope
    @ExperimentalCoroutinesApi
    override fun execute(query: String,
    callbackInput: SearchCallback?) {
    ...
    repository.searchTweet(query)
    .onEach {
    onSuccess(it)
    }
    .catch { e ->
    onError(e)
    }.launchIn(scope)
    }
    @raulhernandezl

    View full-size slide

  143. override fun cancel() {
    ...
    }
    private val scope =
    CoroutineScope(taskThreading.uiDispatcher() + SupervisorJob())
    SearchTweetUseCase: How to cancel with Structured
    concurrency?
    @raulhernandezl

    View full-size slide

  144. override fun cancel() {
    callback = null
    scope.cancel()
    }
    private val scope =
    CoroutineScope(taskThreading.uiDispatcher() + SupervisorJob())
    SearchTweetUseCase: cancellation with Structured
    concurrency
    @raulhernandezl

    View full-size slide

  145. SearchTweetUseCase: How to cancel observables?
    .subscribeOn(taskThreading.computation())
    .observeOn(taskThreading.ui())
    @raulhernandezl
    @Override
    public void dispose() {
    ...
    }

    View full-size slide

  146. @Override
    public void dispose() {
    if (disposable != null && !disposable.isDisposed()) {
    disposable.dispose();
    disposable = null;
    }
    callback = null;
    }
    SearchTweetUseCase: observables disposal
    .subscribeOn(taskThreading.computation())
    .observeOn(taskThreading.ui())
    @raulhernandezl

    View full-size slide

  147. private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    Recap: Schedulers for RxJava
    @raulhernandezl

    View full-size slide

  148. class TaskThreadingImpl
    @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    ...
    }
    Can we use the same RxJava schedulers / dispatchers?
    @raulhernandezl

    View full-size slide

  149. class TaskThreadingImpl
    @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    ...
    }
    Yes, we can re-use existing RxJava schedulers
    YES!
    @raulhernandezl

    View full-size slide

  150. Scheduler.asCoroutineDispatcher -> Converts scheduler to
    CoroutineDispatcher
    asCoroutineDispatcher
    @raulhernandezl

    View full-size slide

  151. Scheduler.asCoroutineDispatcher -> Converts scheduler to
    CoroutineDispatcher
    asCoroutineDispatcher
    kotlinx-coroutines-rx2
    @raulhernandezl

    View full-size slide

  152. class TaskThreadingImpl
    @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    private val computationCoroutineContext =
    computationScheduler.asCoroutineDispatcher()
    private val ioCoroutineContext = ioScheduler.asCoroutineDispatcher()
    private val mainCoroutineContext = mainScheduler.asCoroutineDispatcher()
    TaskThreading: same Schedulers transformed to
    CoroutineDispatchers
    kotlinx-coroutines-rx2
    @raulhernandezl

    View full-size slide

  153. ...
    fun getSearchTweets(query: String): Flow>
    = flow> {
    ...
    }.flowOn(taskThreading.ioDispatcher()).asObserv..
    Reminder: TweetsRepository doesn’t need to return
    Observable anymore, just Flow
    @raulhernandezl

    View full-size slide

  154. View Delegate Design
    @raulhernandezl

    View full-size slide

  155. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    View Delegate
    starts
    executes requests
    View
    Listener
    Flows Flows
    Flows
    Channels
    as Flows
    injects
    VIEW DELEGATE with CHANNELS
    @raulhernandezl

    View full-size slide

  156. class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    private val publishSubject: PublishSubject =
    PublishSubject.create()
    ...
    }
    ViewDelegate: RxJava + Kotlin with PublishSubject
    @raulhernandezl

    View full-size slide

  157. class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    private val publishSubject: PublishSubject =
    PublishSubject.create()
    fun prepareViewDelegateListener(val view: SearchView) {
    val listener = ViewListener(publishSubject)
    view.setOnQueryTextListener(listener)
    }
    }
    ViewDelegate: RxJava + Kotlin with PublishSubject
    @raulhernandezl

    View full-size slide

  158. class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun onDispose() {
    view.setOnQueryTextListener(null)
    disposable.dispose()
    }
    fun observeSubject(): Disposable {
    ...
    }
    }
    ViewDelegate: RxJava disposal
    @raulhernandezl

    View full-size slide

  159. class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun prepareViewDelegateListener(val view: SearchView): Flow=
    ...
    }
    ViewDelegate: Flows with channels as flows
    @raulhernandezl

    View full-size slide

  160. @ExperimentalCoroutinesApi
    @FlowPreview
    class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun prepareViewDelegateListener(val view: SearchView): Flow =
    (channelFlow {
    // define listener
    ...
    })
    }
    ViewDelegate: Flows with channelFlow builder
    @raulhernandezl

    View full-size slide

  161. @ExperimentalCoroutinesApi
    @FlowPreview
    class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun prepareViewDelegateListener(val view: SearchView): Flow =
    (channelFlow {
    // define listener
    awaitClose {
    // close listener
    }
    })
    }
    ViewDelegate: Flows self-closing themselves, awaitClose
    @raulhernandezl

    View full-size slide

  162. @ExperimentalCoroutinesApi
    @FlowPreview
    class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun prepareViewDelegateListener(val view: SearchView): Flow =
    (channelFlow {
    // define listener
    awaitClose {
    // close listener
    }
    }).flowOn(taskThreading.ioDispatcher())
    }
    ViewDelegate: Flows with flowOn
    @raulhernandezl

    View full-size slide

  163. @ExperimentalCoroutinesApi
    @FlowPreview
    class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun prepareViewDelegateListener(val view: SearchView): Flow =
    (channelFlow {
    val listener = ViewListener(channel)
    ...
    awaitClose {
    ...
    }
    }).flowOn(taskThreading.ioDispatcher())
    }
    ViewDelegate: Flows with channelFlow builder channel
    @raulhernandezl

    View full-size slide

  164. @ExperimentalCoroutinesApi
    @FlowPreview
    class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun prepareViewDelegateListener(val view: SearchView): Flow =
    (channelFlow {
    val listener = ViewListener(channel)
    ...
    awaitClose {
    ...
    }
    }).flowOn(taskThreading.ioDispatcher())
    }
    ViewDelegate: Flows with channelFlow builder channel
    @raulhernandezl

    View full-size slide

  165. @ExperimentalCoroutinesApi
    @FlowPreview
    class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun prepareViewDelegateListener(val view: SearchView): Flow =
    (channelFlow {
    val listener = ViewListener(channel)
    view.setOnQueryTextListener(listener)
    awaitClose {
    view.setOnQueryTextListener(null)
    }
    }).flowOn(taskThreading.ioDispatcher())
    }
    ViewDelegate: Flows setting the query listener
    @raulhernandezl

    View full-size slide

  166. class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    private val publishSubject: PublishSubject =
    PublishSubject.create()
    fun prepareViewDelegateListener(val view: SearchView) {
    val listener = ViewListener(publishSubject)
    view.setOnQueryTextListener(listener)
    }
    fun onDispose() {
    view.setOnQueryTextListener(null)
    disposable.dispose()
    }
    }
    ViewDelegate: RxJava needs to manually dispose the view
    and disposable
    @raulhernandezl

    View full-size slide

  167. class ViewListener constructor(
    private val publishSubject: PublishSubject
    ): SearchView.OnQueryTextListener {
    ´
    override fun onQueryTextChange(query: String): Boolean {
    ...
    return true
    }
    override fun onQueryTextSubmit(query: String) = false
    }
    ViewListener: injected PublishSubject
    @raulhernandezl

    View full-size slide

  168. class ViewListener constructor(
    private val publishSubject: PublishSubject
    ): SearchView.OnQueryTextListener {
    ´
    override fun onQueryTextChange(query: String): Boolean {
    publishSubject.onNext(query)
    return true
    }
    override fun onQueryTextSubmit(query: String) = false
    }
    ViewListener: RxJava with publishSubject
    @raulhernandezl

    View full-size slide

  169. class ViewListener(
    @ExperimentalCoroutinesApi
    constructor(
    private val queryChannel: SendChannel
    ): SearchView.OnQueryTextListener {
    ´
    override fun onQueryTextChange(query: String): Boolean {
    ...
    return true
    }
    override fun onQueryTextSubmit(query: String) = false
    }
    ViewListener: Channels with SendChannel
    @raulhernandezl

    View full-size slide

  170. class ViewListener(
    @ExperimentalCoroutinesApi
    constructor(
    private val queryChannel: SendChannel
    ): SearchView.OnQueryTextListener {
    ´
    override fun onQueryTextChange(query: String): Boolean {
    queryChannel.offer(query)
    return true
    }
    override fun onQueryTextSubmit(query: String) = false
    }
    ViewListener: Channels with SendChannel
    @raulhernandezl

    View full-size slide

  171. class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun observeSubject(): Disposable =
    publishSubject
    .observeOn(taskThreading.ui())
    .subscribeOn(taskThreading.computation())
    ...
    }
    ViewDelegate: RxJava observes
    @raulhernandezl

    View full-size slide

  172. class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun observeSubject(): Disposable =
    publishSubject
    .observeOn(taskThreading.ui())
    .subscribeOn(taskThreading.computation())
    .debounce(600, TimeUnit.MILLISECONDS)
    .distinctUntilChanged()
    .filter { filterQuery(it) }
    .subscribe (
    ...
    )
    }
    ViewDelegate: RxJava operators
    @raulhernandezl

    View full-size slide

  173. class ViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun observeSubject(): Disposable =
    publishSubject
    .observeOn(taskThreading.ui())
    .subscribeOn(taskThreading.computation())
    .debounce(600, TimeUnit.MILLISECONDS)
    .distinctUntilChanged()
    .filter { filterQuery(it) }
    .subscribe (
    presenter.searchTweet(it),
    presenter::showProblemHappened
    )
    }
    ViewDelegate: RxJava uses Presenter
    @raulhernandezl

    View full-size slide

  174. fun observeChannelAsFlow() {
    scope.launch {
    declareViewDelegate()
    ...
    }
    }
    ViewDelegate: as Flow
    @raulhernandezl

    View full-size slide

  175. @ExperimentalCoroutinesApi
    @FlowPreview
    fun observeChannelAsFlow() {
    scope.launch {
    declareViewDelegate()
    .debounce(600)
    .distinctUntilChanged()
    .filter { filterQuery(it) }
    .flowOn(taskThreading.computationDispatcher())
    ...
    }
    }
    ViewDelegate: with Flow
    @raulhernandezl

    View full-size slide

  176. @ExperimentalCoroutinesApi
    @FlowPreview
    fun observeChannelAsFlow() {
    scope.launch {
    declareViewDelegate()
    .debounce(600)
    .distinctUntilChanged()
    .filter { filterQuery(it) }
    .flowOn(taskThreading.computationDispatcher())
    .catch { captureError(e) }
    .collect {
    try {
    presenter.searchTweet(it)
    } catch (e: Exception) {
    presenter::showProblemHappened
    }
    }
    }
    }
    ViewDelegate: with Flow
    @raulhernandezl

    View full-size slide

  177. @ExperimentalCoroutinesApi
    @FlowPreview
    fun observeChannelAsFlow() {
    declareViewDelegate()
    .debounce(600)
    .distinctUntilChanged()
    .filter { filterQuery(it) }
    .flowOn(taskThreading.computationDispatcher())
    .onEach {
    presenter.searchTweet(it)
    }
    .catch { presenter::showProblemHappened }
    .launchIn(scope)
    }
    ViewDelegate: with Flow
    @raulhernandezl

    View full-size slide

  178. All done?
    @raulhernandezl

    View full-size slide

  179. All done?
    NO!
    @raulhernandezl

    View full-size slide

  180. TaskThreading Migration
    @raulhernandezl

    View full-size slide

  181. class TaskThreadingImpl @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    private val computationCoroutineContext =
    computationScheduler.asCoroutineDispatcher()
    private val ioCoroutineContext = ioScheduler.asCoroutineDispatcher()
    private val mainCoroutineContext = mainScheduler.asCoroutineDispatcher()
    Reuse of schedulers as dispatchers
    kotlinx-coroutines-rx2
    @raulhernandezl

    View full-size slide

  182. class TaskThreadingImpl @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    private val computationCoroutineContext = // ?
    private val ioCoroutineContext = // ?
    private val mainCoroutineContext = // ?
    Remove reuse schedulers as dispatchers
    @raulhernandezl

    View full-size slide

  183. class TaskThreadingImpl @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    private val computationCoroutineContext: CoroutineDispatcher = Default
    private val ioCoroutineContext: CoroutineDispatcher = IO
    private val mainCoroutineContext: CoroutineDispatcher = Main
    Use native Coroutine Dispatchers
    @raulhernandezl

    View full-size slide

  184. class TaskThreadingImpl @Inject constructor() : TaskThreading {
    private val computationCoroutineContext: CoroutineDispatcher = Default
    private val ioCoroutineContext: CoroutineDispatcher = IO
    private val mainCoroutineContext: CoroutineDispatcher = Main
    override fun ui(): CoroutineContext = mainCoroutineContext
    override fun io(): CoroutineContext = ioCoroutineContext
    override fun computation(): CoroutineContext = computationCoroutineContext
    }
    Expose new dispatchers
    @raulhernandezl

    View full-size slide

  185. Demo time
    @raulhernandezl

    View full-size slide

  186. Lessons learned
    @raulhernandezl

    View full-size slide

  187. Flow does (mostly) all we need
    @raulhernandezl

    View full-size slide

  188. Exchangeable
    @raulhernandezl

    View full-size slide

  189. Structured Concurrency
    @raulhernandezl

    View full-size slide

  190. Structured Concurrency is gone when
    using Observables and Dispatchers
    simultaneously
    @raulhernandezl

    View full-size slide

  191. Imperative vs Declarative
    programming
    @raulhernandezl

    View full-size slide

  192. Next Steps
    @raulhernandezl

    View full-size slide

  193. Wait for stable Flow APIs
    @raulhernandezl

    View full-size slide

  194. Wait for stable Flow APIs
    @Deprecated
    @raulhernandezl

    View full-size slide

  195. Channels and Flows into the same API
    @raulhernandezl

    View full-size slide

  196. Special thanks
    @raulhernandezl
    Manuel Vivo
    @manuelvicnt

    View full-size slide

  197. Thank you.
    @raulhernandezl

    View full-size slide

  198. Questions?
    @raulhernandezl

    View full-size slide