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

Ffc500baeba9a1024e2c8273203c9f90?s=128

Raul Hernandez Lopez

April 20, 2020
Tweet

Transcript

  1. Fueled Reactive apps with Asynchronous Flow @raulhernandezl

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

  3. Agenda @raulhernandezl

  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
  5. Use case @raulhernandezl

  6. Tweets Search (old) sample app @raulhernandezl

  7. Tweets Search (old) sample app Search for Tweets with the

    #hashtag @raulhernandezl
  8. Participation needed! @raulhernandezl

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

  10. Participation needed! Let’s all tweet #tweetswithflow Add the hashtag at

    the first 140 characters @raulhernandezl
  11. Search for the #hashtag @raulhernandezl #tweetswithflow

  12. Migration Strategy @raulhernandezl

  13. Legacy means Refactoring @raulhernandezl

  14. Steps to follow @raulhernandezl

  15. Analysing previous Architecture @raulhernandezl

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

  17. Use Case Repository Network data source DB data source Business

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

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

    DB data source Model View Presenter (MVP) + Clean Architecture @raulhernandezl
  20. Check backwards compatibility requirements @raulhernandezl

  21. @raulhernandezl

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

    being @raulhernandezl
  23. Analysing pinpoints & connections @raulhernandezl

  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  34. Benefits @raulhernandezl

  35. Re-usage of existent components @raulhernandezl

  36. Layers collaboration @raulhernandezl

  37. Asynchronous communication: Streams @raulhernandezl

  38. Conversations like Streams @raulhernandezl

  39. Conversation / Start request Raul @raulhernandezl

  40. Conversations / Acknowledge received Cristina ACK @raulhernandezl

  41. Conversations / Starts stream Cristina ACK @raulhernandezl

  42. Conversations / Stream received Raul Cristina ACK @raulhernandezl

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

  44. Basics @raulhernandezl

  45. One shot operations @raulhernandezl

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

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

    @raulhernandezl
  48. ONE SHOT OPERATIONS @raulhernandezl SINGLE Single<T> SUSPEND FUNCTION OBJECT suspend

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

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

    () -> T MAYBE Maybe<T> SUSPEND FUNCTION NULLABLE suspend () -> T? COMPLETABLE Completable SUSPEND FUNCTION UNIT suspend () -> Unit
  51. Streams: Hot & Cold @raulhernandezl

  52. STREAMS: COLD OBSERVABLE / FLOWABLE @raulhernandezl

  53. STREAMS: COLD OBSERVABLE / FLOWABLE FLOW @raulhernandezl

  54. STREAMS: HOT SUBJECT @raulhernandezl

  55. STREAMS: HOT SUBJECT CHANNELS @raulhernandezl

  56. Threading @raulhernandezl

  57. THREADING SCHEDULER @raulhernandezl

  58. THREADING SCHEDULER DISPATCHER @raulhernandezl

  59. Lifecycle @raulhernandezl

  60. LIFECYCLE DISPOSABLE @raulhernandezl

  61. LIFECYCLE DISPOSABLE SCOPE @raulhernandezl

  62. SCOPE STRUCTURED CONCURRENCY @raulhernandezl

  63. STRUCTURED CONCURRENCY RECURSIVE CLEAN UP @raulhernandezl

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

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

  66. CANCELLATION RECURSIVE CLEAN UP AVOIDS MEMORY LEAKS @raulhernandezl

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

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

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

    PROCESS 1 @raulhernandezl
  70. STRUCTURED CONCURRENCY ERROR PROPAGATION @raulhernandezl

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

  72. Implementation @raulhernandezl

  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
  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
  75. Data Sources Design: Network & DB @raulhernandezl

  76. Presenter Use Case Repository Network data source DB data source

    requests executes View / Callbacks View Delegate View Listener FLOWS to OBSERVABLES @raulhernandezl
  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
  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
  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
  80. Single<List<TweetApiModel>> search(String token, String query); Case 1) NetworkDataSource with Single

    @raulhernandezl
  81. public Single<List<TweetApiModel>> 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
  82. public Single<List<TweetApiModel>> 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
  83. fun search(token: String, query: String) : Single<Either<Throwable, List<TweetApiModel>>> Case 1)

    NetworkDataSource in Kotlin @raulhernandezl
  84. suspend search(token: String, query: String): Either<Throwable, List<TweetApiModel>> Case 1) NetworkDataSource

    with suspend
  85. override suspend fun search(token: String, query: String) : Either<Throwable, List<TweetApiModel>>

    { ... } Case 1) NetworkDataSource with suspend @raulhernandezl
  86. override suspend fun search(token: String, query: String) : Either<Throwable, List<TweetApiModel>>

    { 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
  87. @ExperimentalCoroutinesApi override fun search(token: String, query: String) : Flow<Either<Throwable, List<TweetApiModel>>>

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

    = flow<Either<Throwable, List<TweetApiModel>>> { ... }.flowOn(taskThreading.ioDispatcher()) flowOn operator @raulhernandezl
  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
  90. @ExperimentalCoroutinesApi override fun search(token: String, query: String) : Flow<Either<Throwable, List<TweetApiModel>>>

    = flow<Either<Throwable, List<TweetApiModel>>> { 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
  91. fun search(token: String, query: String) : Observable<Either<Throwable, List<TweetApiModel>>> Case 2)

    NetworkDataSource with Observable @raulhernandezl
  92. @ExperimentalCoroutinesApi override fun search(token: String, query: String) : Observable<Either<Throwable, List<TweetApiModel>>>

    = flow<Either<Throwable, List<TweetApiModel>>> { 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
  93. Flow.asObservable -> Converts the given flow to asObservable @raulhernandezl kotlinx-coroutines-rx2

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

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

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

    @raulhernandezl
  97. @Query("SELECT * FROM ${Tweet.TABLE_NAME} WHERE tweet_id IN(:tweetIds) ORDER BY created_at

    DESC") fun retrieveAllTweetsForTweetsIdsRx(tweetIds: List<String>): Observable<List<Tweet>> Database datasource (DAO) @raulhernandezl
  98. Repository Design Step 1: RxJava approach @raulhernandezl

  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
  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
  101. override fun search(token: String, query: String) : Single<Either<Throwable, List<TweetApiModel>>> {

    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
  102. val eitherResult = networkDataSource.search(token, query).await() val tweets = mapperTweets.map(eitherResult) Case

    1) TweetsRepository waiting for Single @raulhernandezl kotlinx-coroutines-rx2
  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
  104. fun getSearchTweets(token: String, query: String): Observable<List<Tweet>> { return networkDataSource.search(token, query)

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

    .map { either -> mapperTweets.map(either) } ... } Case 2) TweetsRepository: Kotlin + RxJava
  106. fun getSearchTweets(token: String, query: String): Observable<List<Tweet>> { 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
  107. fun getSearchTweets(token: String, query: String): Observable<List<Tweet>> { 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
  108. @ExperimentalCoroutinesApi ... @FlowPreview @InternalCoroutinesApi Case 2) TweetsRepository: Flow @raulhernandezl

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

    @raulhernandezl
  110. ... fun getSearchTweets(token: String, query: String): Flow<List<Tweet>> { return networkDataSource

    .search(token, query) .map { either -> mapperTweets.map(either) } ... } Case 2) TweetsRepository: Flow @raulhernandezl
  111. ... fun getSearchTweets(token: String, query: String): Flow<List<Tweet>> { 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
  112. ... fun getSearchTweets(token: String, query: String): Flow<List<Tweet>> { 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
  113. ... fun getSearchTweets(token: String, query: String): Flow<List<Tweet>> { 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
  114. @Query("SELECT * FROM ${Tweet.TABLE_NAME} WHERE tweet_id IN(:tweetIds) ORDER BY created_at

    DESC") fun retrieveAllTweetsForTweetsIdsFlow(tweetIds: List<String>): Flow<List<Tweet>> Database datasource (DAO) with Flow @raulhernandezl
  115. ... fun getSearchTweets(token: String, query: String): Observable<List<Tweet>> { 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
  116. Repository Design Step 2: Suspend approach @raulhernandezl

  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
  118. suspend fun getSearchTweets(query: String): List<Tweet> { val accessToken = tokenDataSource.getAccessToken()

    ?: throw BadTokenException("No token was found") val token = accessToken.token ... } Case 1) TweetsRepository: Suspend @raulhernandezl
  119. suspend fun getSearchTweets(query: String): List<Tweet> { 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
  120. suspend fun getSearchTweets(query: String): List<Tweet> { 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
  121. suspend fun getSearchTweets(query: String): List<Tweet> { 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?
  122. suspend fun getSearchTweets(query: String): List<Tweet> { 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!
  123. suspend fun getSearchTweets(query: String): List<Tweet> { 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!
  124. @Query("SELECT * FROM ${Tweet.TABLE_NAME} WHERE tweet_id IN(:tweetIds) ORDER BY created_at

    DESC") suspend fun retrieveAllTweetsForTweetsIds(tweetIds: List<String>): List<Tweet> Database datasource (DAO) with suspend @raulhernandezl
  125. @ExperimentalCoroutinesApi ... @FlowPreview @InternalCoroutinesApi Case 2) TweetsRepository: Flow @raulhernandezl

  126. ... fun getSearchTweets(query: String) = flow<List<Tweet>> { ... val tweetIds

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

    = tweetQueryJoinDataSource.retrieveAllTweetsIdAQuery(query) emit(tweetsDataSource.retrieveAllTweetsForTweetsIds(tweetIds)) }.flowOn(taskThreading.ioDispatcher()) Case 1.. 2) TweetsRepository: emit a suspend List @raulhernandezl YES!
  128. ... fun getSearchTweets(query: String) = flow<List<Tweet>> { ... // 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!
  129. ... fun getSearchTweets(query: String): Observable<List<Tweet>> = flow<List<Tweet>> { ... }.flowOn(taskThreading.ioDispatcher()).asObservable()

    Case 2) Repository: Flow to Observable @raulhernandezl kotlinx-coroutines-rx2
  130. Use Case Design @raulhernandezl

  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
  132. @ActivityScope class SearchTweetUseCase @Inject constructor( private val tweetsRepository: TweetsRepository, private

    val taskThreading: TaskThreading ) : UseCase<SearchCallback> { SearchTweetUseCase constructor dependencies @raulhernandezl
  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
  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
  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
  136. @ExperimentalCoroutinesApi override fun execute(query: String, callbackInput: SearchCallback?) { callback =

    callbackInput callback?.onShowLoader() ... } SearchTweetUseCase: Kotlin @raulhernandezl
  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())
  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())
  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())
  140. SearchTweetUseCase: onEach @ExperimentalCoroutinesApi override fun execute(query: String, callbackInput: SearchCallback?) {

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

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

    ... repository.searchTweet(query) .onEach { onSuccess(it) } .catch { e -> onError(e) }.launchIn(scope) } @raulhernandezl
  143. override fun cancel() { ... } private val scope =

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

    val scope = CoroutineScope(taskThreading.uiDispatcher() + SupervisorJob()) SearchTweetUseCase: cancellation with Structured concurrency @raulhernandezl
  145. SearchTweetUseCase: How to cancel observables? .subscribeOn(taskThreading.computation()) .observeOn(taskThreading.ui()) @raulhernandezl @Override public

    void dispose() { ... }
  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
  147. private val computationScheduler = Schedulers.computation() private val ioScheduler = Schedulers.io()

    private val mainScheduler = AndroidSchedulers.mainThread() Recap: Schedulers for RxJava @raulhernandezl
  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
  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
  150. Scheduler.asCoroutineDispatcher -> Converts scheduler to CoroutineDispatcher asCoroutineDispatcher @raulhernandezl

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

  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
  153. ... fun getSearchTweets(query: String): Flow<List<Tweet>> = flow<List<Tweet>> { ... }.flowOn(taskThreading.ioDispatcher()).asObserv..

    Reminder: TweetsRepository doesn’t need to return Observable anymore, just Flow @raulhernandezl
  154. View Delegate Design @raulhernandezl

  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
  156. class ViewDelegate constructor( private val presenter: SearchTweetPresenter ) { private

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

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

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

    prepareViewDelegateListener(val view: SearchView): Flow<String>= ... } ViewDelegate: Flows with channels as flows @raulhernandezl
  160. @ExperimentalCoroutinesApi @FlowPreview class ViewDelegate constructor( private val presenter: SearchTweetPresenter )

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

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

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

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

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

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

    val publishSubject: PublishSubject<String> = 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
  167. class ViewListener constructor( private val publishSubject: PublishSubject<String> ): SearchView.OnQueryTextListener {

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

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

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

    { ´ override fun onQueryTextChange(query: String): Boolean { queryChannel.offer(query) return true } override fun onQueryTextSubmit(query: String) = false } ViewListener: Channels with SendChannel @raulhernandezl
  171. class ViewDelegate constructor( private val presenter: SearchTweetPresenter ) { fun

    observeSubject(): Disposable = publishSubject .observeOn(taskThreading.ui()) .subscribeOn(taskThreading.computation()) ... } ViewDelegate: RxJava observes @raulhernandezl
  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
  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
  174. fun observeChannelAsFlow() { scope.launch { declareViewDelegate() ... } } ViewDelegate:

    as Flow @raulhernandezl
  175. @ExperimentalCoroutinesApi @FlowPreview fun observeChannelAsFlow() { scope.launch { declareViewDelegate() .debounce(600) .distinctUntilChanged()

    .filter { filterQuery(it) } .flowOn(taskThreading.computationDispatcher()) ... } } ViewDelegate: with Flow @raulhernandezl
  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
  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
  178. All done? @raulhernandezl

  179. All done? NO! @raulhernandezl

  180. TaskThreading Migration @raulhernandezl

  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
  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
  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
  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
  185. Demo time @raulhernandezl

  186. Lessons learned @raulhernandezl

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

  188. Exchangeable @raulhernandezl

  189. Structured Concurrency @raulhernandezl

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

    @raulhernandezl
  191. Imperative vs Declarative programming @raulhernandezl

  192. Next Steps @raulhernandezl

  193. Wait for stable Flow APIs @raulhernandezl

  194. Wait for stable Flow APIs @Deprecated @raulhernandezl

  195. Channels and Flows into the same API @raulhernandezl

  196. Special thanks @raulhernandezl Manuel Vivo @manuelvicnt

  197. Thank you. @raulhernandezl

  198. Questions? @raulhernandezl