RxJava/Kotlin Observable method call chain - how to terminate? - android

I want to implement method to edit a note, save it to local database (cache) and then send it to the server as a POST request. I am learning RxJava and I wanted to create Observable from the note and then apply transformations on it, like to map it to an Entity model and saving. The issue that my method returns Completable and this chain returns Observable<Completable>. How to unwrap the Completable from this Observable which I used only to start RxJava stuff. Each editNote() methods returns a Completable.
override fun editNote(note: Note): Completable {
return Observable.just(note)
.map { mapper.mapToEntity(it) }
.map { noteEntity ->
factory.getCacheDataStore().editNote(noteEntity)
.andThen { factory.getRemoteDataStore().editNote(noteEntity) }
}
}
=======================================================
UPDATE
Finally, I managed to find "a solution" but I am not sure it is correct :-)
override fun editNote(note: Note): Completable {
return Observable.just(note)
.map { mapper.mapToEntity(it) }
.flatMapCompletable { noteEntity ->
factory.getCacheDataStore().editNote(noteEntity)
.andThen { factory.getRemoteDataStore().editNote(noteEntity) }
}
}

You're looking for flatMapCompletable instead of map, because map just intercepts the stream and maps the emissions to another type, while 'flatMap' (or it's siblings), from the docs:
Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable.
You can see it's marble diagram in Here

Related

Kotlin rxjava switchIfEmpty

I'm trying to understand the logic of switchIfEmpty operator. I will be very thankful for every explanation.
I have a local database (Room) and remote server. My goal is to implement logic with switchIfEmpty to check if there is data in local DB to take it and if local DB is empty to call from remote. The process starts in activity where I subscribe to Observable:
private fun subscribeOnDataChanges() = with(viewModel) {
requestNextPage()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
filmsAdapter.addItems(it)
}, {
errorEvent.value = ERROR_MSG
it.printStackTrace()
}).addTo(autoDisposable)
}
Then the methods in the View Model:
fun requestNextPage(): Observable<List<Film>> {
return requestPageOfFilms()
}
private fun requestPageOfFilms(): Observable<List<Film>> =
interactor.requestPageOfFilmsFromDataSource()
And, finally the method with switchIfEmpty in the Interactor:
fun requestPageOfFilmsFromDataSource(): Observable<List<Film>> {
return repo.getPageOfFilmsInCategory(category).filter { it.isNotEmpty() }.switchIfEmpty(
getFromRemote(category)
)
}
private fun getFromRemote(category: String): Observable<List<Film>> {
return convertSingleApiToObservableDtoList(
retrofitService.getFilms(
category, API.KEY, "ru-RU", NEXT_PAGE
)
)
}
I cannot understand the next things:
Why, when local db (repo) is NOT empty, getFromRemote() is called?
If local db is empty, why network call in the method getFromRemote() is not performed? No matters, that I subscribed in the activity? Because if I add the subscription inside the switchIfEmpty(), the network call is performed.
To answer your questions :
Why, when local db (repo) is NOT empty, getFromRemote() is called?
Because this function convertSingleApiToObservableDtoList() is being evaluated at Assembly Time and not Subscription Time (https://github.com/ReactiveX/RxJava#assembly-time). The result is that the Observable object is eagerly evaluated.
If local db is empty, why network call in the method getFromRemote() is not performed? No matters, that I subscribed in the activity? Because if I add the subscription inside the switchIfEmpty(), the network call is performed.
Because the upstream has not emitted a value yet or completed. You are conflating an empty List with an empty Observable (https://reactivex.io/documentation/operators/defaultifempty.html)
A better solution to your problem would be using the flatMap or even switchMap operator if you expect a chatty upstream and only care about latest result, something like :
fun requestPageOfFilmsFromDataSource(): Observable<List<Film>> {
return repo.getPageOfFilmsInCategory(category)
.flatMap { films ->
if (films.isNotEmpty()) {
Observable.just(films)
} else getFromRemote(category)
}
}

Get object value from Observable (RxJava, Retrofit)

I'm trying to learn to use Rxjava to make Api calls with retrofit. I have to make multiple api calls in a loop. I'm struggling with getting the value in my subscriber.
#GET("pokemon" + "/{id}")
fun getPokemonData(#Path("id") id: Int):
Observable<Pokemon>
I'm expecting to get a Pokemon object in my Subscriber but instead I get a Observable. How do I transform it to a Pokemon object?
Observable.fromIterable(list)
.flatMap { it ->
Observable
.just(it.url)
.map { PokeApi.retrofitService.getPokemonData(getPokemonIdFromUrl(it))
}
}
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//onNext --I'm expecting to get a Pokemon Object here, instead I get a Observable<Pokemon>
}, {//onError} , {// do something when all api calls are done?})
My goal is to make api calls with ids in the "list" and get "notified" when all the api calls are finished. Is this the correct approach to solve this problem ?
The problems lies here:
Observable
.just(it.url)
.map { PokeApi.retrofitService.getPokemonData(getPokemonIdFromUrl(it)) }
When you use map it maps to the return object from getPokemonData. You probably want to flatMap it:
Observable
.just(it.url)
.flatMap { PokeApi.retrofitService.getPokemonData(getPokemonIdFromUrl(it)) }
which not only maps the result but flattens it too so you don't get an observable, but the result of that observable.

Why doest Completable.andThen is not work?

I am developing an Android App using Kotlin, RxJava(RxKotlin).
I need to combine two Completables.
interface RetrofitApi {
#Get
fun getA(): Completable
#Post
fun save(obj: Any): Completable
}
I have a Retrofit code like upper.
And I tryed to chain them in one stream like:
fun getAndSave() {
retrofitApi.getA()
.andThen {
retrofitApi.save(any())
}
.subscribe()
}
But the second Completable is not run!
When I changed upper code like below code, it works fine!
fun getAndSave() {
val secondCall = retrofitApi.save(any())
retrofitApi.getA()
.andThen(secondCall)
.subscribe()
}
What difference???
They are calling two different overloads of the andThen method.
In the first one, the save Completable is only created after the getA Completable completes. However it is never subscribed to. (a lambda is passed in which is ran when getA Completable completes)
In the second case (with the secondCall variable), the save Completable is created 'right away' but not subscribed to until getA Completable completes. (a CompletableSource is passed in which is subscribed to once getA Completable completes)

CompletableTransformer is not applying subscribeOn and observeOn to the Upstream

I'm creating offline first app as my side project using rxKotlin, MVVM + Clean Architecture and yesterday I decided to get ride off boilerplate subscribeOn and observeOn by using transformers. I quickly realized that apply function of transformers are ignored.
Here is code of my base completable use case (interactor):
abstract class CompletableUseCase(private val transformer: CompletableTransformer) {
abstract fun createCompletable(data: Map<String, Any>? = null) : Completable
fun completable(data: Map<String, Any>? = null) : Completable {
return createCompletable(data).compose(transformer)
}
}
And here is implementation of specific interactor:
class SaveRouteInteractor(
transformer: CompletableTransformer,
private val routeRepository: RouteRepository
) : CompletableUseCase(transformer) {
companion object {
private const val PARAM_ROUTE = "param_route"
}
fun saveRoute(route: Route) : Completable {
val data = HashMap<String, Route>()
data[PARAM_ROUTE] = route
return completable(data)
}
override fun createCompletable(data: Map<String, Any>?): Completable {
val routeEntity = data?.get(PARAM_ROUTE)
routeEntity?.let {
return routeRepository.saveRoute(routeEntity as Route)
} ?: return Completable.error(IllegalArgumentException("Argument #route must be provided."))
}
}
My custom transformer that is passed to the constructor of SaveRouteInteractor:
class IOCompletableTransformer(private val mainThreadScheduler: Scheduler) : CompletableTransformer {
override fun apply(upstream: Completable): CompletableSource {
return upstream.subscribeOn(Schedulers.io()).observeOn(mainThreadScheduler)
}
}
And implementation of RouteRepository method:
override fun saveRoute(route: Route): Completable {
return localRouteSource.saveRoute(route)
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
.flatMapCompletable { localRouteSource.updateRouteID(route.routeId, it) }
}
I'm using Room as my local source so after calling save interactor in my ViewModel I'm getting IlligalStateException telling me that I'm not allowed to access database on the main thread.
Maybe I'm missing something but it seems that transform function is ignored. I debugged this method and it is applying subscribeOn and observeOn to the upstream.
Thanks for help in advance,
Pace!
It's hard to tell you where the issue is because the code is partial.
For example here:
return localRouteSource.saveRoute(route)
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
.flatMapCompletable { localRouteSource.updateRouteID(route.routeId, it) }
I suppose the localRouteSource.saveRoute() is using the interactor you show us but it is not clear how remoteRouteSource.saveRoute() or localRouteSource.updateRouteID() are implemented.
they also need to be subscribed on the IO thread.
As a rule of thumb you should switch thread when you KNOW that you need it.
In other words, you should use subscribeOn() in places where you know you are doing IO as close as possible to the actual job. ObserveOn instead is to be used when you know you need to obtain those results in the UI thread and that you might get in some other thread.
in your example there's absolutely no need to keep using observeOn(MAIN_THREAD), the only time you do need it (I suppose) is when you want to show the result.
A couple of other things:
This code
override fun createCompletable(data: Map<String, Any>?): Completable {
val routeEntity = data?.get(PARAM_ROUTE)
routeEntity?.let {
return routeRepository.saveRoute(routeEntity as Route)
} ?: return Completable.error(IllegalArgumentException("Argument #route must be provided."))
}
it is evaluated at the time when the method is called rather then when the completable is subscribed.
In other words it break the Rx contract and compute data?.get(PARAM_ROUTE) when you call the method. If it is immutable there's no much difference, but if it can change value during execution it should be wrapped in a Completable.defer { }
Finally, here
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
you are modyfing something outside the chain (route.routeId = localID), this is called a side effect.
be careful with those kind of stuff, Rx is build in a way that is safer to be used with immutable objects.
I personally wouldn't mind too much as long as you understand what's going on and when it could create issues.

Data transformations with RxJava in Kotlin

I'm using Kotlin and I have an Observable<List<FlickPhoto>> model. Each FlickrPhoto contains a List, and each Photos model contains a List<Photo>.
I want to transform a List<FlickrPhoto> to a List<PhotoEntity>, which is basically another model with less data, and wrap it into an Observable (in order to finally get an Observable<List<PhotoEntity>>).
How can I do this in the best RxJava way?
I have created a PhotoEntityMapper class with the following method that I'm sure could be properly used for the purpose:
override fun transform(photoCollection: List<Photo>): List<PhotoEntity>
Thanks in advance!
The solution in your comment does work:
// original
remoteDataSource
.getPhotos(photoTag)
.flatMap({ t: FlickrPhoto ->
Observable
.just(serviceMapper.transform(t.photos.photo)) // <-- not lazy
})
Here you have slightly different options:
// v1
remoteDataSource
.getPhotos(photoTag)
.flatMap {
Observable
.just(it.photos.photo)
.map { serviceMapper.transform(it) } // <-- lazy transformation
}
// v2
remoteDataSource
.getPhotos(photoTag)
.flatMapIterable { it }
.map { serviceMapper.transform(it.photos.photo) }
No option is that much better than the others. Normally I would go with v2 because it is shorter.
But, I don't know what the serviceMapper does. For example, if it involved network and I wanted to execute it in another Scheduler, then v1 is nice because you can subscribe the transformations in another thread (which you can't do in the original version because the transformation is pre-computed eaguerly). Example:
// v1 - with scheduler
remoteDataSource
.getPhotos(photoTag)
.flatMap {
Observable
.just(it.photos.photo)
.map { serviceMapper.transform(it) } // <-- lazy transformation
.subscribeOn(Schedulers.io()) // <-- schedule on io()
}

Categories

Resources