Slide 1

Slide 1 text

Fueled Reactive apps with Asynchronous Flow @raulhernandezl

Slide 2

Slide 2 text

Software Engineer Raul Hernandez Lopez @ Twitter raulh82vlc @raulhernandezl

Slide 3

Slide 3 text

Agenda @raulhernandezl

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Use case @raulhernandezl

Slide 6

Slide 6 text

Tweets Search (old) sample app @raulhernandezl

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Participation needed! @raulhernandezl

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Search for the #hashtag @raulhernandezl #tweetswithflow

Slide 12

Slide 12 text

Migration Strategy @raulhernandezl

Slide 13

Slide 13 text

Legacy means Refactoring @raulhernandezl

Slide 14

Slide 14 text

Steps to follow @raulhernandezl

Slide 15

Slide 15 text

Analysing previous Architecture @raulhernandezl

Slide 16

Slide 16 text

Repository Network data source DB data source Data Layer @raulhernandezl

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Check backwards compatibility requirements @raulhernandezl

Slide 21

Slide 21 text

@raulhernandezl

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Analysing pinpoints & connections @raulhernandezl

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Benefits @raulhernandezl

Slide 35

Slide 35 text

Re-usage of existent components @raulhernandezl

Slide 36

Slide 36 text

Layers collaboration @raulhernandezl

Slide 37

Slide 37 text

Asynchronous communication: Streams @raulhernandezl

Slide 38

Slide 38 text

Conversations like Streams @raulhernandezl

Slide 39

Slide 39 text

Conversation / Start request Raul @raulhernandezl

Slide 40

Slide 40 text

Conversations / Acknowledge received Cristina ACK @raulhernandezl

Slide 41

Slide 41 text

Conversations / Starts stream Cristina ACK @raulhernandezl

Slide 42

Slide 42 text

Conversations / Stream received Raul Cristina ACK @raulhernandezl

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Basics @raulhernandezl

Slide 45

Slide 45 text

One shot operations @raulhernandezl

Slide 46

Slide 46 text

ONE SHOT OPERATIONS SINGLE / MAYBE / COMPLETABLE @raulhernandezl

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Streams: Hot & Cold @raulhernandezl

Slide 52

Slide 52 text

STREAMS: COLD OBSERVABLE / FLOWABLE @raulhernandezl

Slide 53

Slide 53 text

STREAMS: COLD OBSERVABLE / FLOWABLE FLOW @raulhernandezl

Slide 54

Slide 54 text

STREAMS: HOT SUBJECT @raulhernandezl

Slide 55

Slide 55 text

STREAMS: HOT SUBJECT CHANNELS @raulhernandezl

Slide 56

Slide 56 text

Threading @raulhernandezl

Slide 57

Slide 57 text

THREADING SCHEDULER @raulhernandezl

Slide 58

Slide 58 text

THREADING SCHEDULER DISPATCHER @raulhernandezl

Slide 59

Slide 59 text

Lifecycle @raulhernandezl

Slide 60

Slide 60 text

LIFECYCLE DISPOSABLE @raulhernandezl

Slide 61

Slide 61 text

LIFECYCLE DISPOSABLE SCOPE @raulhernandezl

Slide 62

Slide 62 text

SCOPE STRUCTURED CONCURRENCY @raulhernandezl

Slide 63

Slide 63 text

STRUCTURED CONCURRENCY RECURSIVE CLEAN UP @raulhernandezl

Slide 64

Slide 64 text

SCOPE PROCESS 3 PROCESS 2 CANCELLATION PROCESS 1 @raulhernandezl

Slide 65

Slide 65 text

SCOPE PROCESS 3 PROCESS 2 CANCELLATION PROCESS 1 @raulhernandezl

Slide 66

Slide 66 text

CANCELLATION RECURSIVE CLEAN UP AVOIDS MEMORY LEAKS @raulhernandezl

Slide 67

Slide 67 text

AVOID SIDE-EFFECTS ON CANCELLATION SCOPE + SupervisorJob @raulhernandezl

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

STRUCTURED CONCURRENCY ERROR PROPAGATION @raulhernandezl

Slide 71

Slide 71 text

STRUCTURED CONCURRENCY ERROR PROPAGATION CATCH / HANDLE THEM @raulhernandezl

Slide 72

Slide 72 text

Implementation @raulhernandezl

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Data Sources Design: Network & DB @raulhernandezl

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

@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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

Repository Design Step 1: RxJava approach @raulhernandezl

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

@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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

Repository Design Step 2: Suspend approach @raulhernandezl

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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!

Slide 124

Slide 124 text

@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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

Use Case Design @raulhernandezl

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

@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

Slide 134

Slide 134 text

@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

Slide 135

Slide 135 text

@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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

@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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

View Delegate Design @raulhernandezl

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

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

Slide 160

Slide 160 text

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

Slide 161

Slide 161 text

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

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

@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

Slide 177

Slide 177 text

@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

Slide 178

Slide 178 text

All done? @raulhernandezl

Slide 179

Slide 179 text

All done? NO! @raulhernandezl

Slide 180

Slide 180 text

TaskThreading Migration @raulhernandezl

Slide 181

Slide 181 text

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

Slide 182

Slide 182 text

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

Slide 183

Slide 183 text

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

Slide 184

Slide 184 text

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

Slide 185

Slide 185 text

Demo time @raulhernandezl

Slide 186

Slide 186 text

Lessons learned @raulhernandezl

Slide 187

Slide 187 text

Flow does (mostly) all we need @raulhernandezl

Slide 188

Slide 188 text

Exchangeable @raulhernandezl

Slide 189

Slide 189 text

Structured Concurrency @raulhernandezl

Slide 190

Slide 190 text

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

Slide 191

Slide 191 text

Imperative vs Declarative programming @raulhernandezl

Slide 192

Slide 192 text

Next Steps @raulhernandezl

Slide 193

Slide 193 text

Wait for stable Flow APIs @raulhernandezl

Slide 194

Slide 194 text

Wait for stable Flow APIs @Deprecated @raulhernandezl

Slide 195

Slide 195 text

Channels and Flows into the same API @raulhernandezl

Slide 196

Slide 196 text

Special thanks @raulhernandezl Manuel Vivo @manuelvicnt

Slide 197

Slide 197 text

Thank you. @raulhernandezl

Slide 198

Slide 198 text

Questions? @raulhernandezl