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

Fueled Reactive apps with Asynchronous Flow & StateFlow to Sync with the UI

Fueled Reactive apps with Asynchronous Flow & StateFlow to Sync with the UI

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?
Can we fully avoid using callbacks now? StateFlow to the rescue!

Conferences or meetups:
- Droidcon EMEA #dcEMEA #droidconEMEA (October the 9th 2020)
- Madrid Android Developer Group (MADG) meetup - Spanish language (November the 10th 2020)
- Kotlin London (KDU) meetup (December the 2nd 2020)
- Droidcon APAC #dcAPAC #droidconAPAC (December the 14th 2020)

Raul Hernandez Lopez

October 09, 2020
Tweet

More Decks by Raul Hernandez Lopez

Other Decks in Programming

Transcript

  1. Fueled Reactive apps with
    Asynchronous Flow
    &
    StateFlow to Sync with the UI
    @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. Basics
    4. Implementation
    5. Lessons learned
    6. Next steps
    @raulhernandezl
    AGENDA

    View full-size slide

  5. Use case
    @raulhernandezl

    View full-size slide

  6. Tweets Search
    sample app
    Search for Tweets
    with the #hashtag
    @raulhernandezl

    View full-size slide

  7. Migration Strategy
    @raulhernandezl

    View full-size slide

  8. Legacy means Refactoring
    @raulhernandezl

    View full-size slide

  9. Steps to follow
    @raulhernandezl

    View full-size slide

  10. Analysing previous Architecture
    @raulhernandezl

    View full-size slide

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

    View full-size slide

  12. Use Case Repository
    Network data
    source
    DB data source
    Business Layer
    @raulhernandezl
    requests
    results
    results

    View full-size slide

  13. Presenter Use Case Repository
    Network data
    source
    DB data source
    Model View Presenter (MVP) + Clean Architecture
    @raulhernandezl
    requests
    executes
    results results
    results

    View full-size slide

  14. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    Model View Presenter (MVP) + Clean Architecture
    @raulhernandezl
    View Delegate
    View
    Listener
    starts
    injects
    requests
    executes
    types
    results results
    results

    View full-size slide

  15. Check backwards compatibility
    requirements
    @raulhernandezl

    View full-size slide

  16. @raulhernandezl

    View full-size slide

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

    View full-size slide

  18. Analysing pinpoints &
    connections
    @raulhernandezl

    View full-size slide

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

    View full-size slide

  20. 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
    types
    results results results

    View full-size slide

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

    View full-size slide

  22. 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
    types
    results results results

    View full-size slide

  23. 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
    types
    results results results

    View full-size slide

  24. 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
    types
    results results results
    Flow

    View full-size slide

  25. 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
    types
    results results results
    Flow
    Flow

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. Presenter Use Case Repository
    View /
    Callbacks
    Network data
    source
    DB data source
    View Delegate
    starts
    executes requests
    View
    Listener
    Channels
    as Flows
    injects
    CAN WE REMOVE CALLBACKS to COMMUNICATE?
    @raulhernandezl
    Flow
    Flow
    types
    results results results

    View full-size slide

  29. Presenter Use Case Repository
    View
    Network data
    source
    DB data source
    View Delegate
    starts
    executes requests
    View
    Listener
    Channels
    as Flows
    injects
    CALLBACKS MIGRATION: STATEFLOW
    @raulhernandezl
    Flow
    Flow
    results
    types
    StateFlow StateFlow

    View full-size slide

  30. Presenter Use Case Repository
    View
    Network data
    source
    DB data source
    View Delegate
    starts
    executes requests
    View
    Listener
    Channels
    as Flows
    injects
    CALLBACKS MIGRATION: STATEFLOW HANDLER
    @raulhernandezl
    Flow
    Flow
    StateFlow
    StateFlow
    Handler
    results
    types
    StateFlow
    initializes
    processes

    View full-size slide

  31. Benefits
    @raulhernandezl

    View full-size slide

  32. Re-usage of existent elements
    @raulhernandezl

    View full-size slide

  33. Layers collaboration
    @raulhernandezl

    View full-size slide

  34. Basics
    @raulhernandezl

    View full-size slide

  35. What is an Open Stream?
    @raulhernandezl

    View full-size slide

  36. Open Streams are
    conversations like
    Raul Cristina
    ACK 1..n ACK 1..n
    @raulhernandezl

    View full-size slide

  37. What if open streams are NOT
    needed?
    @raulhernandezl

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

  41. Threading
    @raulhernandezl

    View full-size slide

  42. THREADING
    SCHEDULER
    @raulhernandezl

    View full-size slide

  43. THREADING
    SCHEDULER
    DISPATCHER
    @raulhernandezl

    View full-size slide

  44. Lifecycle
    @raulhernandezl

    View full-size slide

  45. LIFECYCLE
    DISPOSABLE
    @raulhernandezl

    View full-size slide

  46. LIFECYCLE
    DISPOSABLE
    SCOPE
    @raulhernandezl

    View full-size slide

  47. SCOPE
    STRUCTURED CONCURRENCY
    @raulhernandezl

    View full-size slide

  48. STRUCTURED CONCURRENCY
    RECURSIVE CLEAN UP
    @raulhernandezl

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  51. CANCELLATION
    RECURSIVE CLEAN UP
    AVOIDS MEMORY LEAKS
    @raulhernandezl

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  55. Implementation
    @raulhernandezl

    View full-size slide

  56. Gradle dependencies
    @raulhernandezl
    build.gradle
    ...
    // Kotlin Coroutines
    implementation
    "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
    implementation
    "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
    // Kotlin Standard Library
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_stdlib_version"
    kotlin_coroutines_version = '1.4.0'
    kotlin_stdlib_version = '1.4.0'

    View full-size slide

  57. Data Sources Design:
    Network & DB
    @raulhernandezl

    View full-size slide

  58. Presenter Use Case Repository
    Network data
    source
    DB data source
    requests
    executes
    View /
    Callbacks
    View Delegate
    View
    Listener
    SUSPEND to SINGLE
    @raulhernandezl
    starts
    injects
    results results
    types
    results

    View full-size slide

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

    View full-size slide

  60. @NotNull
    public Single> search(
    @NotNull String token,
    @NotNull String query
    ) {
    return twitterApi.search(
    requestsIOHandler.getTokenFormatted(token),query)
    ...
    }
    NetworkDataSource w/ Single (Java)
    @raulhernandezl

    View full-size slide

  61. @NotNull
    public Single> search(
    @NotNull String token,
    @NotNull String query
    ) {
    return twitterApi.search(
    requestsIOHandler.getTokenFormatted(token),query)
    .filter(requestsIOHandler::searchHasNoErrorResponse)
    .map(requestsIOHandler::getTweets)
    .flatMapSingle(Single::just));
    }
    NetworkDataSource w/ Single (Java)
    @raulhernandezl

    View full-size slide

  62. override suspend fun search(token: String, query: String)
    : Either> {
    ...
    }
    NetworkDataSource w/ suspend
    @raulhernandezl

    View full-size slide

  63. 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()))
    }
    }
    NetworkDataSource w/ suspend
    @raulhernandezl

    View full-size slide

  64. 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())
    }
    }
    }
    NetworkDataSource with... rxSingle
    @raulhernandezl
    kotlinx-coroutines-rx2

    View full-size slide

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

    View full-size slide

  66. Repository Design
    Step 1: Learning (or Naive)
    RxJava approach
    @raulhernandezl

    View full-size slide

  67. 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
    starts
    injects
    types
    results results
    types
    results

    View full-size slide

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

  69. fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource.search(token, query)
    .subscribeOn(taskThreading.io())
    .observeOn(taskThreading.computation())
    ...
    }
    TweetsRepository: Kotlin + RxJava threading

    View full-size slide

  70. fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource.search(token, query)
    .subscribeOn(taskThreading.io())
    .observeOn(taskThreading.computation())
    .map { either -> mapperTweets.map(either) }
    ...
    }
    TweetsRepository: Kotlin + RxJava map

    View full-size slide

  71. fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource.search(token, query)
    .subscribeOn(taskThreading.io())
    .observeOn(taskThreading.computation())
    .map { either -> mapperTweets.map(either) }
    .doOnNext { tweetsToAdd ->
    tweetQueryJoinDataSource.deleteTweets(query)
    tweetsDataSource.insertTweets(tweetsToAdd)
    insertQuery(query)
    tweetQueryJoinDataSource.insertQueryAndTweetsId(
    convert(tweetsToAdd, query))
    ...
    }
    TweetsRepository: Kotlin + RxJava doOnNext

    View full-size slide

  72. fun getSearchTweets(token: String, query: String): Observable> {
    return networkDataSource.search(token, query)
    .subscribeOn(taskThreading.io())
    .observeOn(taskThreading.computation())
    .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)
    }
    }
    }
    TweetsRepository: Kotlin + RxJava flatMap

    View full-size slide

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

    View full-size slide

  74. 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))
    }
    ...
    }
    TweetsRepository w/ onEach
    @raulhernandezl

    View full-size slide

  75. 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)
    }
    }
    TweetsRepository w/ flatMapConcat
    @raulhernandezl

    View full-size slide

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

    View full-size slide

  77. 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())
    }
    TweetsRepository w/ flowOn & IO dispatcher
    @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()
    override fun ui(): Scheduler = mainScheduler
    override fun io(): Scheduler = ioScheduler
    override fun computation(): Scheduler = computationScheduler
    }
    TaskThreading with RxJava Schedulers
    @raulhernandezl

    View full-size slide

  79. 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 Coroutines Dispatchers
    @raulhernandezl

    View full-size slide

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

    View full-size slide

  81. @Singleton
    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

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

    View full-size slide

  83. @Singleton
    class TaskThreadingImpl
    @Inject constructor() : TaskThreading {
    private val computationScheduler = Schedulers.computation()
    private val ioScheduler = Schedulers.io()
    private val mainScheduler = AndroidSchedulers.mainThread()
    override fun uiDispatcher(): CoroutineDispatcher =
    mainScheduler.asCoroutineDispatcher()
    override fun ioDispatcher(): CoroutineDispatcher =
    ioScheduler.asCoroutineDispatcher()
    override fun computationDispatcher(): CoroutineDispatcher =
    computationScheduler.asCoroutineDispatcher()
    TaskThreading: reuse Schedulers transformed into
    CoroutineDispatchers
    kotlinx-coroutines-rx2
    @raulhernandezl

    View full-size slide

  84. 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()
    }
    TweetsRepository w/ Flow to Observable
    @raulhernandezl
    kotlinx-coroutines-rx2

    View full-size slide

  85. Repository Design
    Step 2: (Pro) Suspend approach
    @raulhernandezl

    View full-size slide

  86. 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
    starts
    injects
    results results
    types
    results

    View full-size slide

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

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

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

  90. 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: Can Flow be returned into a
    suspend function?
    ???

    View full-size slide

  91. 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 cannot be returned into
    suspend function!
    @raulhernandezl
    NO!

    View full-size slide

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

  93. @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

  94. Case 2) TweetsRepository: Flow
    @raulhernandezl

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  97. fun getSearchTweets(query: String): Flow>
    = 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 2) TweetsRepository: flow builder & emit more than
    once
    @raulhernandezl
    YES!

    View full-size slide

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

    View full-size slide

  99. Use Case Design
    @raulhernandezl

    View full-size slide

  100. 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
    starts
    injects
    results results
    types
    results

    View full-size slide

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

    View full-size slide

  102. UseCase contract w/ callback
    interface UseCase {
    fun execute(param: String, callbackInput: T?)
    fun cancel()
    }
    @raulhernandezl

    View full-size slide

  103. @Override
    void execute(@NotNull String query, @Nullable SearchCallback callback) {
    this.callback = callback;
    if(callback != null) { callback.onShowLoader(); }
    }
    SearchTweetUseCase: RxJava + Java
    @raulhernandezl

    View full-size slide

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

    View full-size slide

  105. @Override
    void execute(@NotNull 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
    .map( // some computation )
    .subscribe(
    callback::onSuccess,// action for each stream
    callback::onError); // error handling
    }
    SearchTweetUseCase: Rx threading & map downstream
    @raulhernandezl

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  108. override fun execute(query: String,
    callbackInput: SearchCallback?) {
    callback = callbackInput
    callback?.onShowLoader()
    scope.launch {
    repository.searchTweet(query)
    .map { // some computation }
    .flowOn(taskThreading.computationDispatcher())
    ...
    }
    }
    SearchTweetUseCase: Kotlin Flow map in another thread
    w/ flowOn upstream
    @raulhernandezl
    private val scope = CoroutineScope(taskThreading.uiDispatcher()
    + SupervisorJob())

    View full-size slide

  109. override fun execute(query: String,
    callbackInput: SearchCallback?) {
    callback = callbackInput
    callback?.onShowLoader()
    scope.launch {
    repository.searchTweet(query)
    .map { // some computation }
    .flowOn(taskThreading.computationDispatcher())
    .catch {
    callback::onError
    }.collect { tweets ->// UI actions for each stream
    callback.onSuccess(tweets)
    }
    }
    }
    SearchTweetUseCase: Flow + Kotlin using collect
    @raulhernandezl
    private val scope = CoroutineScope(taskThreading.uiDispatcher()
    + SupervisorJob())

    View full-size slide

  110. override fun execute(query: String,
    callbackInput: SearchCallback?) {
    callback = callbackInput
    callback?.onShowLoader()
    repository.searchTweet(query)
    .map { // some computation }
    .flowOn(taskThreading.computationDispatcher())
    .onEach { tweets -> // UI actions for each stream
    callback.onSuccess(tweets)
    }.catch {
    callback::onError
    }.launchIn(scope)
    }
    SearchTweetUseCase: Flow + Kotlin using launchIn
    @raulhernandezl
    private val scope = CoroutineScope(taskThreading.uiDispatcher()
    + SupervisorJob())

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  114. @Override
    public void cancel() {
    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

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

  116. View Delegate Design
    @raulhernandezl

    View full-size slide

  117. 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
    starts
    injects
    results results
    types
    results

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  121. @ActivityScope
    class SearchViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    @ExperimentalCoroutinesApi
    fun prepareViewDelegateListener(val view: SearchView): Flow =
    (channelFlow {
    // define listener
    val listener = SearchViewListener(channel)
    ...
    }).flowOn(taskThreading.ioDispatcher())
    }
    @raulhernandezl
    SearchViewDelegate: Flows with channelFlow builder

    View full-size slide

  122. @ExperimentalCoroutinesApi
    fun prepareViewDelegateListener(val view: SearchView): Flow =
    (channelFlow {
    val listener = SearchViewListener(channel)
    view.setOnQueryTextListener(listener)
    awaitClose {
    view.setOnQueryTextListener(null)
    }
    }).flowOn(taskThreading.ioDispatcher())
    SearchViewDelegate: Flows setting/closing the query listener
    @raulhernandezl

    View full-size slide

  123. @ActivityScope
    class SearchViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun observeSubject(): Disposable {
    ...
    }
    fun cancel() {
    view.setOnQueryTextListener(null)
    if (disposable != null && !disposable.isDisposed()) {
    disposable.dispose()
    disposable = null
    }
    presenter.cancel()
    }
    }
    SearchViewDelegate: RxJava needs to manually dispose the
    view and disposable
    @raulhernandezl

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  128. @ActivityScope
    class SearchViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun observeSubject(): Disposable =
    publishSubject
    .observeOn(taskThreading.ui())
    .subscribeOn(taskThreading.computation())
    ...
    }
    SearchViewDelegate w/ RxJava subject
    @raulhernandezl

    View full-size slide

  129. @ActivityScope
    class SearchViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun observeSubject(): Disposable =
    publishSubject
    .observeOn(taskThreading.ui())
    .subscribeOn(taskThreading.computation())
    .debounce(600, TimeUnit.MILLISECONDS)
    .distinctUntilChanged()
    .filter { query -> filterQuery(query) }
    .subscribe (
    ...
    )
    }
    SearchViewDelegate w/ RxJava Subject downstream
    @raulhernandezl

    View full-size slide

  130. @ActivityScope
    class SearchViewDelegate constructor(
    private val presenter: SearchTweetPresenter
    ) {
    fun observeSubject(): Disposable =
    publishSubject
    .observeOn(taskThreading.ui())
    .subscribeOn(taskThreading.computation())
    .debounce(600, TimeUnit.MILLISECONDS)
    .distinctUntilChanged()
    .filter { query -> filterQuery(query) }
    .subscribe (
    presenter::searchTweet,
    presenter::showProblemHappened
    )
    }
    SearchViewDelegate w/ RxJava starts Presenter
    @raulhernandezl

    View full-size slide

  131. @FlowPreview
    fun observeChannelAsFlow() {
    declareViewDelegate()
    .debounce(600)
    .distinctUntilChanged()
    .filter { query -> filterQuery(query) }
    .flowOn(taskThreading.computationDispatcher())
    ...
    }
    SearchViewDelegate: w/ Flow flowOn upstream
    @raulhernandezl

    View full-size slide

  132. @FlowPreview
    fun observeChannelAsFlow() {
    declareViewDelegate()
    .debounce(600)
    .distinctUntilChanged()
    .filter { query -> filterQuery(query) }
    .flowOn(taskThreading.computationDispatcher())
    .onEach { query ->
    presenter.searchTweet(query)
    }
    .catch { presenter::showProblemHappened }
    .launchIn(scope)
    }
    fun cancel() {
    scope.cancel()
    presenter.cancel()
    }
    SearchViewDelegate lifecycle handling and stream
    @raulhernandezl
    private val scope = CoroutineScope(taskThreading.uiDispatcher() + SupervisorJob())

    View full-size slide

  133. All done to get rid of RxJava?
    @raulhernandezl

    View full-size slide

  134. NO!
    @raulhernandezl
    All done to get rid of RxJava?

    View full-size slide

  135. TaskThreading Migration
    @raulhernandezl

    View full-size slide

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

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

  138. Synchronous communication
    Design
    @raulhernandezl

    View full-size slide

  139. Presenter Use Case Repository
    View
    Network data
    source
    DB data source
    View Delegate
    starts
    executes requests
    View
    Listener
    Flows
    Channels
    as Flows
    injects
    @raulhernandezl
    Flows
    Flows
    results
    types
    StateFlow StateFlow
    STATEFLOW to get rid of CALLBACKS in USE CASE

    View full-size slide

  140. @RetainedScope
    class SearchTweetUseCase @Inject constructor(
    private val tweetsRepository: TweetsRepository,
    private val taskThreading: TaskThreading
    ) : UseCase {
    ) : UseCaseFlow {
    SearchTweetUseCase recap: constructor dependencies
    @raulhernandezl

    View full-size slide

  141. SearchTweetUseCase update: remove callback
    interface UseCaseStateFlow {
    fun execute(param: String, callbackInput: SearchCallback?)
    fun cancel()
    fun getStateFlow(): StateFlow
    }
    @raulhernandezl

    View full-size slide

  142. SearchTweetUseCase update: remove callback
    interface UseCaseStateFlow {
    fun execute(param: String, callbackInput: SearchCallback?)
    fun cancel()
    fun getStateFlow(): StateFlow
    }
    @raulhernandezl
    StateFlow can be passed across Java & Kotlin files

    View full-size slide

  143. @RetainedScope
    class SearchTweetUseCase @Inject constructor(
    private val tweetsRepository: TweetsRepository,
    private val taskThreading: TaskThreading
    ) : UseCaseFlow {
    private val tweetsUIStateFlow = MutableStateFlow(null)
    override fun getStateFlow(): StateFlow = tweetsUIStateFlow
    SearchTweetUseCase w/ MutableStateFlow
    @raulhernandezl

    View full-size slide

  144. @RetainedScope
    class SearchTweetUseCase @Inject constructor(
    private val tweetsRepository: TweetsRepository,
    private val taskThreading: TaskThreading
    ) : UseCaseFlow {
    private val tweetsUIStateFlow = MutableStateFlow(null)
    override fun getStateFlow(): StateFlow = tweetsUIStateFlow
    SearchTweetUseCase w/ MutableStateFlow
    distinctUntilChanged by default
    @raulhernandezl
    StateFlow uses distinctUntilChanged by default

    View full-size slide

  145. TweetsUIState w/ Results state
    @raulhernandezl
    /**
    * UI States defined for StateFlow in the workflow
    */
    sealed class TweetsUIState {
    data class ListResultsUIState(val tweets: List): TweetsUIState()
    }

    View full-size slide

  146. TweetsUIState w/ Error state
    @raulhernandezl
    /**
    * UI States defined for StateFlow in the workflow
    */
    sealed class TweetsUIState {
    data class ListResultsUIState(val tweets: List): TweetsUIState()
    data class ErrorUIState(val msg: String): TweetsUIState()
    }

    View full-size slide

  147. TweetsUIState w/ Empty state
    @raulhernandezl
    /**
    * UI States defined for StateFlow in the workflow
    */
    sealed class TweetsUIState {
    data class ListResultsUIState(val tweets: List): TweetsUIState()
    data class ErrorUIState(val msg: String): TweetsUIState()
    object EmptyUIState: TweetsUIState()
    }

    View full-size slide

  148. TweetsUIState w/ Loading state
    @raulhernandezl
    /**
    * UI States defined for StateFlow in the workflow
    */
    sealed class TweetsUIState {
    data class ListResultsUIState(val tweets: List): TweetsUIState()
    data class ErrorUIState(val msg: String): TweetsUIState()
    object EmptyUIState: TweetsUIState()
    object LoadingUIState: TweetsUIState()
    }

    View full-size slide

  149. override fun execute(query: String) {
    callback?.onShowLoader()
    tweetsStateFlow.value = TweetsUIState.LoadingUIState
    }
    SearchTweetUseCase: Loading state propagation
    @raulhernandezl

    View full-size slide

  150. override fun execute(query: String) {
    tweetsStateFlow.value = TweetsUIState.LoadingUIState
    repository.searchTweet(query)
    .onEach { tweets ->
    val stateFlow =
    if (tweets.isEmpty()) {
    TweetsUIState.EmptyUIState
    }
    tweetsStateFlow.value = stateFlow
    }
    .catch { e ->
    ...
    }.launchIn(scope)
    }
    SearchTweetUseCase: Empty state propagation
    @raulhernandezl

    View full-size slide

  151. override fun execute(query: String) {
    tweetsStateFlow.value = TweetsUIState.LoadingUIState
    repository.searchTweet(query)
    .onEach { tweets ->
    val stateFlow =
    if (tweets.isEmpty()) {
    TweetsUIState.EmptyUIState
    } else {
    TweetsUIState.ListResultsUIState(tweets)
    }
    tweetsStateFlow.value = stateFlow
    }
    .catch { e ->
    ...
    }.launchIn(scope)
    }
    SearchTweetUseCase: List results state propagation
    @raulhernandezl

    View full-size slide

  152. override fun execute(query: String) {
    tweetsStateFlow.value = TweetsUIState.LoadingUIState
    repository.searchTweet(query)
    .onEach { tweets ->
    val stateFlow =
    if (tweets.isEmpty()) {
    TweetsUIState.EmptyUIState
    } else {
    TweetsUIState.ListResultsUIState(tweets)
    }
    tweetsStateFlow.value = stateFlow
    }
    .catch { e ->
    tweetsStateFlow.value = TweetsUIState.ErrorUIState(e.msg))
    }.launchIn(scope)
    }
    SearchTweetUseCase: Error state propagation
    @raulhernandezl

    View full-size slide

  153. Presenter Use Case Repository
    View
    Network data
    source
    DB data source
    View Delegate
    starts
    executes requests
    View
    Listener
    Flows
    Channels
    as Flows
    injects
    STATEHANDLER (KOTLIN) to COLLECT STATE
    @raulhernandezl
    Flows
    Flows
    StateFlow
    StateFlow
    Handler
    results
    types
    StateFlow
    initializes
    processes

    View full-size slide

  154. SearchTweetPresenter (Java): constructor
    @raulhernandezl
    @ActivityScope
    public class SearchTweetPresenter {
    @NotNull
    private final SearchTweetUseCase tweetSearchUseCase;
    @Inject
    public SearchTweetPresenter(
    @NotNull SearchTweetUseCase tweetSearchUseCase
    ) {
    this.tweetSearchUseCase = tweetSearchUseCase;
    }
    public void cancel() {
    tweetSearchUseCase.cancel();
    }

    View full-size slide

  155. SearchTweetPresenter (Java) responsibilities
    @raulhernandezl
    @ActivityScope
    public class SearchTweetPresenter {
    ...
    public void searchTweets(@NotNull final String query) {
    if (callback == null && view != null) {
    callback = new SearchCallbackImpl(view);
    }
    tweetSearchUseCase.execute(query, callback);
    }
    @NotNull
    public StateFlow getStateFlow() {
    return tweetSearchUseCase.getStateFlow();
    }
    StateFlow can be passed across Java & Kotlin files

    View full-size slide

  156. SearchViewDelegate gets StateFlow from Presenter
    @raulhernandezl
    @ActivityScope
    class SearchViewDelegate @Inject constructor(
    private val presenter: SearchTweetPresenter,
    private val taskThreading: TaskThreading
    ) {
    private fun getStateFlow(): StateFlow = presenter.stateFlow

    View full-size slide

  157. SearchViewDelegate initialises UI w/ StateFlow
    @raulhernandezl
    @ActivityScope
    class SearchViewDelegate @Inject constructor(
    private val presenter: SearchTweetPresenter,
    private val taskThreading: TaskThreading
    ) {
    private fun getStateFlow(): StateFlow = presenter.stateFlow
    fun initialiseProcessingQuery(
    searchView: SearchView,
    uiResults: TweetsListUI?
    ) {
    uiResults?.apply {
    initStateFlow(getStateFlow())
    }
    ...
    }

    View full-size slide

  158. TweetsListUI / View (Java) delegates to StateFlowHandler
    @raulhernandezl
    public class TweetsListUI extends ViewUI {
    @Inject
    SearchViewDelegate viewDelegate;
    @Inject
    StateFlowHandler stateFlowHandler;
    public void initStateFlow(
    @NotNull StateFlow extends TweetsUIState> stateFlow
    ) {
    stateFlowHandler.init(stateFlow, this);
    stateFlowHandler.processStateFlowCollection();
    }
    StateFlow can be passed across Java & Kotlin files

    View full-size slide

  159. StateFlowHandler: constructor
    @raulhernandezl
    @ActivityScope
    class StateFlowHandler @Inject constructor(
    taskThreading: TaskThreading
    ) {
    private var tweetsListUI: TweetsListUI? = null
    private lateinit var stateFlow: StateFlow
    fun init(
    val stateFlow: StateFlow,
    val tweetsListUI: TweetsListUI
    ) {
    stateFlow = stateFlow
    tweetsListUI = tweetsListUI
    }
    ...
    }

    View full-size slide

  160. StateFlowHandler filters StateFlow prior collection
    private lateinit var stateFlow: StateFlow
    fun processStateCollection() {
    stateFlow
    .filter { stateFlow ->
    stateFlow != null
    }
    ...
    }
    @raulhernandezl

    View full-size slide

  161. StateFlowHandler collects StateFlow w/ onEach
    private lateinit var stateFlow: StateFlow
    fun processStateCollection() {
    stateFlow
    .filter { stateFlow ->
    stateFlow != null
    }
    .onEach { stateFlow ->
    tweetsListUI?.handleStates(stateFlow)
    }...
    }
    @raulhernandezl

    View full-size slide

  162. StateFlowHandler w/ StateFlow collection requires a scope
    private val scope = CoroutineScope(taskThreading.ui() + SupervisorJob())
    fun processStateCollection() {
    stateFlow
    .filter { stateFlow ->
    stateFlow != null
    }
    .onEach { stateFlow ->
    tweetsListUI?.handleStates(stateFlow)
    }.launchIn(scope)
    }
    @raulhernandezl
    StateFlow needs a Scope to be collected

    View full-size slide

  163. TweetsListUI handles States
    @raulhernandezl
    fun TweetsListUI.handleStates(stateFlow: TweetsUIState?) {
    when (stateFlow) {
    is TweetsUIState.LoadingUIState -> {
    showLoading()
    }
    is TweetsUIState.ListResultsUIState -> {
    showResults(stateFlow)
    }
    is TweetsUIState.EmptyUIState -> {
    showEmptyState()
    }
    is TweetsUIState.ErrorUIState -> {
    showError(stateFlow)
    } else -> {
    showEmptyState()
    }
    }
    }

    View full-size slide

  164. StateFlowHandler cancels StateFlow collection
    @raulhernandezl
    @ActivityScope
    class StateFlowHandler @Inject constructor(
    taskThreading: TaskThreading
    ) {
    private val scope = CoroutineScope(taskThreading.ui() + SupervisorJob())
    private var tweetsListUI: TweetsListUI? = null
    fun cancel() {
    scope.cancel()
    tweetsListUI = null
    }

    View full-size slide

  165. TweetsListUI / View lifecycle
    @raulhernandezl
    public class TweetsListUI extends ViewUI {
    @Inject
    SearchViewDelegate viewDelegate;
    @Inject
    StateFlowHandler stateFlowHandler;
    @Override
    public void onDestroy() {
    stateFlowHandler.cancel();
    viewDelegate.cancel();
    super.onDestroy();
    }
    }

    View full-size slide

  166. Fueled Reactive with Flow done!
    @raulhernandezl

    View full-size slide

  167. Lessons learned
    @raulhernandezl

    View full-size slide

  168. Exchangeable
    @raulhernandezl

    View full-size slide

  169. Structured Concurrency
    @raulhernandezl

    View full-size slide

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

    View full-size slide

  171. Imperative vs Declarative
    programming
    @raulhernandezl

    View full-size slide

  172. Pros & Cons
    @raulhernandezl
    RxJava Declarative
    Coroutines + Flow Imperative + Declarative
    RxJava Manual lifecycle handling *by default
    Coroutines + Flow Structured Concurrency
    RxJava Java + Kotlin hybrid projects *
    Coroutines + Flow Perfect for Kotlin & friends

    View full-size slide

  173. Next Steps
    @raulhernandezl

    View full-size slide

  174. Wait for stable Flow APIs
    @raulhernandezl

    View full-size slide

  175. Wait for stable Flow APIs
    @Deprecated
    @raulhernandezl

    View full-size slide

  176. References
    @raulhernandezl
    Fueled Reactive apps with Asynchronous Flow
    1. Use case & Migration Strategy
    2. Asynchronous communication: Streams & Basics
    3. Data layer Implementation
    4. Use Case layer Implementation
    5. View Delegate Implementation
    6. Lessons learned & Next steps
    7. Synchronous communication with the UI using
    StateFlow

    View full-size slide

  177. Special thanks
    @raulhernandezl
    Manuel Vivo
    @manuelvicnt

    View full-size slide

  178. Thank you.
    @raulhernandezl

    View full-size slide

  179. Questions?
    @raulhernandezl

    View full-size slide