Share
Embed
Coroutines and Rx: A battle towards Asynchrony
This was given at Droidcon SF 2019
Amanjeet Singh
November 26, 2019
Transcript
84 slides
-
showsApi.getShowByName("Friends") .flatmap { showsApi.getCharacters(it.id) } .subscribe( { /* do something
with characters*/ }, { /* handle errors retrieving characters*/} ) -
showsApi.getShowByName("Friends") .flatmap { showsApi.getCharacters(it.id) } .subscribe( { /* do something
with characters*/ }, { /* handle errors retrieving characters*/} ) -
showsApi.getShowByName("Friends") .flatmap { showsApi.getCharacters(it.id) } .subscribe( { /* do something
with characters*/ }, { /* handle errors retrieving characters*/} ) -Ben Christensen -
CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){
// do something with characters on UI } } -
I O CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters =
showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } } -
I O 0 sm.item = "Friends" sm.label = 1 CoroutineScope.launch(Dispatchers.IO){
val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } } -
I O 0 sm.item = "Friends" sm.label = 1 showsApi.getShowsByName(“Friends”,
cont) Continuation CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } } -
I O 0 1 show = sm.result sm.label = 2
sm.item = "Friends" sm.label = 1 showsApi.getShowsByName(“Friends”, cont) CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } } -
I O 0 1 show = sm.result sm.label = 2
showsApi.getCharacters(show.id, cont) sm.item = "Friends" sm.label = 1 showsApi.getShowsByName(“Friends”, cont) Continuation CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } } -
I O 0 1 show = sm.result sm.label = 2
showsApi.getCharacters(show.id, cont) sm.item = "Friends" sm.label = 1 showsApi.getShowsByName(“Friends”, cont) Continuation 2 characters = sm.result CoroutineScope.launch(Dispatchers.IO){ val show = showsApi.getShowByName("Friends") val characters = showsApi.getCharacters(show.id) withContext(Dispatchers.Main){ // do something with characters on UI } } -
! ! ! “Do not walk away from complexity, Run”
- Venkat Subramaniam -
moviesApi.getLatestMovies( ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { /* do something with
movies*/ }, { /* handle errors retrieving movies*/} ) HTTP over -
Error Propagation over 1 2 3 5 N N /
N - 4 -1/3 -1 -3 Destructive 4 -
Error Handling • Wrap your response in Result class sealed
class Result<out T> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>() } • Gotta catch them all onError onErrorReturn onErrorResumeNext doOnError onErrorComplete onErrorReturnItem -
Error Handling fun getMovies(): Observable<Result<Response>> { return moviesApi.getMovies() .map {
Result.Success(it) } .onErrorReturn { Result.Error(it) } } -
Error Handling fun getMovies(): Observable<Result<Response>> { return moviesApi.getMovies() .map {
Result.Success(it) } .onErrorReturn { Result.Error(it) } } Result -
Error Handling over • Depends on what Coroutine Builder you
are using: Launch/Async? • Scope Intelligently • Tools for handling ☠ CoroutineExceptionHandler try/catch -
Error Handling over private val context: CoroutineContext = Job() +
Dispatchers.IO val scope = CoroutineScope(context) suspend fun longRunningTask(): Unit { delay(100) throw IllegalArgumentException() } • Setup -
Error Handling over • Install CoroutineExceptionHandler private val ceh =
CoroutineExceptionHandler { _, e -> println("CEH Handled Crash [$e]") } scope.launch(ceh){ launch{ longRunningTask() } } -
Error Handling over • Install try/catch scope.launch(ceh){ try{ async{ longRunningTask()
}.await() } catch(e: Throwable){ println("Caught") } } -
suspend fun <T : Any> apiCall(call: suspend () -> Result<T>,
errorMessage: String): Result<T> { return try { call() } catch (e: Exception) { Result.Error(e) } } Let’s be a pro -
suspend fun <T : Any> apiCall(call: suspend () -> Result<T>,
errorMessage: String): Result<T> { return try { call() } catch (e: Exception) { Result.Error(e) } } Result Let’s be a pro -
suspend fun getLatestMovies(): Result<MovieResults> { return apiCall( { val data
= moviesApiInterface.getInTheatreMovies() return@apiCall Result.Success(data.body()) }, "Couldn't get" ) } So, Now are we pro? Let’s be a pro -
No Let’s be a pro So, Now are we pro?
suspend fun getLatestMovies(): Result<MovieResults> { return apiCall( { val data = moviesApiInterface.getInTheatreMovies() return@apiCall Result.Success(data.body()) }, "Couldn't get" ) } -
Zip Calls in Observable.zip(getPopularMovies(), getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies,
topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io()) -
Zip Calls in Let’s apply TEST rule Observable.zip(getPopularMovies(), getNowPlayingMovies(), BiFunction<Movies,
Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io()) -
Zip Calls in Threading Exception Handling Scalability Transforming results Observable.zip(getPopularMovies(),
getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io()) -
Zip Calls in Threading Exception Handling Scalability Transforming results Observable.zip(getPopularMovies(),
getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io()) -
Zip Calls in Threading Exception Handling Scalability Transforming results Observable.zip(getPopularMovies(),
getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io()) -
Zip Calls in Threading Exception Handling Scalability Transforming results Observable.zip(getPopularMovies(),
getNowPlayingMovies(), BiFunction<Movies, Movies, Movies> { popularMovies, topRatedMovies, -> // Filter from popular and top rated movies }.subscribeOn(Scheduler.io()) -
suspend fun <T1, T2, R> zip(source1: Deferred<T1>, source2: Deferred<T2>, zipper:
(T1, T2) -> R) = coroutineScope { // wait for two calls to complete async { zipper(source1.await(), source2.await()) } } Zip Calls in • Let’s cook zip + -
suspend fun <T1, T2, R> zip(source1: Deferred<T1>, source2: Deferred<T2>, zipper:
(T1, T2) -> R) = coroutineScope { // wait for two calls to complete async { zipper(source1.await(), source2.await()) } } Zip Calls in • Let’s cook zip + Zip Deferred 1 Deferred 2 Zipper Result -
suspend fun <T1, T2, R> zip(source1: Deferred<T1>, source2: Deferred<T2>, zipper:
(T1, T2) -> R) = coroutineScope { // wait for two calls to complete async { zipper(source1.await(), source2.await()) } } Zip Calls in • Let’s cook zip + -
Zip Calls in coroutineScope.launch { val popularMovies = async{ getPopularMovies()
} val topRatedMovies = async{ getTopRatedMovies() } zip(popularMovies, topRatedMovies) { popularResults, topRatedResults -> // Filter from popular and top rated movies return filteredResults }.await() } Threading Exception Handling Scalability Transforming results -
Threading Exception Handling Scalability Transforming results coroutineScope.launch { val popularMovies
= async{ getPopularMovies() } val topRatedMovies = async{ getTopRatedMovies() } zip(popularMovies, topRatedMovies) { popularResults, topRatedResults -> // Filter from popular and top rated movies return filteredResults }.await() } Zip Calls in -
Threading Exception Handling Scalability Transforming results coroutineScope.launch { val popularMovies
= async{ getPopularMovies() } val topRatedMovies = async{ getTopRatedMovies() } zip(popularMovies, topRatedMovies) { popularResults, topRatedResults -> // Filter from popular and top rated movies return filteredResults }.await() } Zip Calls in -
Threading Exception Handling Scalability Transforming results coroutineScope.launch { val popularMovies
= async{ getPopularMovies() } val topRatedMovies = async{ getTopRatedMovies() } zip(popularMovies, topRatedMovies) { popularResults, topRatedResults -> // Filter from popular and top rated movies return filteredResults }.await() } Zip Calls in -
val events = searchButton.clicks() .map { SearchEvent(searchEditText.text.toString()) } Power of
RxBindings State Management through -
State Management through Rich with operators, Adheres to R&FP, exceptional
handling, lifecycle management, etc. val models = events.flatMap { moviesClient.searchMovies(it.searchTerm) } .map { it.results.asResult() } .onErrorReturn { Result.Error(it.toApplicationError()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) -
Rich with operators, Adheres to R&FP, exceptional handling, lifecycle
management, etc. val models = events.flatMap { moviesClient.searchMovies(it.searchTerm) } .map { it.results.asResult() } .onErrorReturn { Result.Error(it.toApplicationError()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) State Management through -
Rich with operators, Adheres to R&FP, exceptional handling, lifecycle
management, etc. val models = events.flatMap { moviesClient.searchMovies(it.searchTerm) } .map { it.results.asResult() } .onErrorReturn { Result.Error(it.toApplicationError()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) State Management through -
Rich with operators, Adheres to R&FP, exceptional handling, lifecycle
management, etc. val models = events.flatMap { moviesClient.searchMovies(it.searchTerm) } .map { it.results.asResult() } .onErrorReturn { Result.Error(it.toApplicationError()) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) State Management through -
• Suspension based reactive streams. • Can be used to
represent streams State Management through -
• Suspension based reactive streams. • Can be used to
represent streams State Management through -
• Rich with operators, Adheres to R&FP, exceptional handling,
lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through -
• Rich with operators, Adheres to R&FP, exceptional handling,
lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through -
• Rich with operators, Adheres to R&FP, exceptional handling,
lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through -
• Rich with operators, Adheres to R&FP, exceptional handling,
lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through -
• Rich with operators, Adheres to R&FP, exceptional handling,
lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } State Management through -
• Rich with operators, Adheres to R&FP, exceptional handling,
lifecycle management, etc. viewModelScope.launch { flowOf(moviesClient.searchMoviesAsync(query).await()) .map { Result.Success(it.results) } .catch { Result.Error(it.toApplicationError()) }.collect { searchStates.postValue(it) } } UI Bindings? State Management through -
References • https://medium.com/the-kotlin-chronicle/coroutine-exceptions-3378f51a7d33 • https://medium.com/@elizarov/ • https://speakerdeck.com/krlrozov/kotlin-coroutines-flow-is-coming • https://speakerdeck.com/ragdroid/flowing-things-not-so-strange-in-the-mvi-world •
https://github.com/amanjeetsingh150/CoroutinesRx • https://medium.com/capital-one-tech/coroutines-and-rxjava-an-asynchronicity-comparison-part-1-asynchronous- programming-e726a925342a • Kotlin Coroutines Slack Channel • https://github.com/android/plaid