So, I have an API call which returns a list of Dog breeds,
and another call that takes the dogBreedIds and fetches dog names for each of these breed IDs.
In onNext, I add these dog names into a list,
and in the doOnComplete, I display these in a recycler view.
Both getDogBreeds and getDogNames return an Observable<List> is an example of rx chain i accomplish this with:
petsRepository.getDogBreeds()
.map { breeds ->
breeds.items.map {
it.id
}
}
.flatMapIterable { listOfIds -> listOfIds }
.flatMap { dogId -> getDogNames(dogId) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete { displayDogNames() }
.subscribe(this::onDogNamesLoaded, this::onError)
What I'm trying to do is, to re-trigger this whole process once in every 60 seconds, and then, compare the response list with the first response I have and display it to the user if the response list has differences (this part is perhaps irrelevant)
I have tried to use Observable.interval(), however I could not succeed in doing this. I have checked plenty of examples with no success.
Please let me know if I should be providing more information on this.
Thanks!
You could go for interval like this:
Observable.interval(0, 60, TimeUnit.SECONDS)
.flatMap {
petsRepository.getDogBreeds()
}.map {...}
The rest should remain pretty much the same.
As for the displaying part - I'm going to assume you're using RecyclerView(correct me if not). If so, then use ListAdapter, DiffUtils will handle displaying differences in lists.
Hope this helps!
Related
Background:
I have an observable that returns mock items and actual items. Mock items are used in ui while actual items are still on the way. Mock items should always arrive first to prevent accidental override of actual items in ui. The premise is that ui layer is dumb, and it does not know which items it is showing nor it's managing them in any way. So the single source of items is this observable. Simplified it looks like this.
val one = Observable.fromCallable { "Mock/cached items" }
val two = Observable.fromCallable { "Live items" }
val result = Observable.concat(listOf(one, two))
.subscribe {
println(it)
}
Now I want to replay this observable, because further downstream it's just one observable of many that are combined together via combineLatest() operator, hence I can't resubscribe to it individually. So, I've added replayWhen(...) after concat() and everything worked properly.
Problem:
Things got tricky when I needed to replay only mock items in some cases vs replaying all items in other cases. Attempt that failed:
val subject1 = PublishSubject.create<Unit>()
val subject2 = PublishSubject.create<Unit>()
val one = Observable.fromCallable { "Mock/cached items" }.repeatWhen { subject1 }
val two = Observable.fromCallable { "Live items" }.repeatWhen { subject2 }
val result = Observable.concat(listOf(one, two))
.subscribe {
println(it)
}
subject1.onNext(Unit)
subject2.onNext(Unit)
Obviously, it prints only mock items while live items aren't called (anytime soon:-)). This is because concat waits for onComplete from first observable, that doesn't happen because of replay operator.
Solutions, and their drawbacks:
Replace concat with merge. This way both observable would work, but there's no guarantee that observable one would fire before the observable two. Is there any way to achieve ordering between observables without concat?
Have replayWhen() after concat, to replay this result observable as a whole, but have something inside .fromCallable{} of observable two to know when to skip it. Not sure what could it be, as a simple flag would be unreliable and dependant on external state that may change sporadically.
Thank you for any other ideas.
I have an observable that on subscribe does a long operation but when a user click on a button I want to notify my observable to do again the long operation because something change.
I don't want to make a new subscription every time the user clicks on the button. Which is the best solution to achieve this?
I would like to know if is possible to use a solution, which use the rxjava simplified way to run code on different threads.
Should I use something like this?
BehaviourProcessor<boolean> processor = BehaviourProcessor.createDefault(true);
public Flowable<List<Item>> getItems(){
return Flowable.create(e -> e.onNext(longOp()))
.subscribeOn(Schedulers.io())
.switchMap(items -> processor.map(notify -> returnItems(notify)));
}
public void notifyChange(){
processor.onNext(true);
}
Android Room library achieve this result, in fact when you subscribe this:
#Query("SELECT * FROM user")
Flowable<List<Item>> getUsers();
Every time you delete an item from database you immediately get the new list from the database in the subscription on next method.
Rather than have getItems() return the observable chain that you have shown, return a shared observable.
Flowable<List<Item>> itemGetter =
Flowable.create(e -> e.onNext( longOp() ) )
.subscribeOn( Schedulers.io() )
.switchMap(items -> processor.map(notify -> returnItems(notify)))
.replay( 1 )
.publish();
Flowable<List<Item>> getItem() {
return itemGetter;
}
This creates only on observer chain that you can subscribe to as many times as you want. However, if there are no subscribers and another subscriber comes along, longOp() will be called again.
If you don't want that to happen, then you should consider using a BehaviorSubject<List<Item>> to cache the value.
I am trying to implement an Android app which needs to obtain a big amount of data from a backend service and save it to a db to later work on it.
The below code describes the process:
itemsService
.getAllItemIds() //This returns Single<List<Int>> from backend
.subscribeOn(Schedulers.io())
.subscribe({
Observable.fromIterable(it)
.map({
itemsService
.getItemById(it) //This gets one item details from backend
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//Add item details to db
}, {
//Some error
})
})
}, {
//Some error
})
I obtain a list of ids and then map each of these ids to a network call to obtain the full object.
This works for a test set of, say, 10 items, but the production set contains over 50 000 ids. It works initially, saving the items, but around 5-10% it grinds to a halt and the app dies.
I assume the reason here would be that Rx keeps the reference between the source and the mapped value.
My question is: is there a way to "pool" the source emissions to, let's say, 10 at a time? Or maybe there is some other mechanism I am not aware of?
You didn't mention what exactly "grinds to a halt" means, but it makes sense that you will get out of memory in real case of 50,000 items, cause you will basically try to create 50,000 threads at once to fetch each items details.
moreover, instead of chaining Observables using operators, you're creating nested chains at subscribe/map, you can read here why you shouldn't.
regarding limiting the work to 10 at a time, there is an flatMap overload for that, at the end it might look something like this:
itemsService
.getAllItemIds() //This returns List<Int> from backend
.flatMapIterable { t -> t }
.flatMap({
itemsService
.getItemById(it) //This gets one item details from backend
.subscribeOn(Schedulers.io())
}, 10) //limit flat map parallelism by desired value
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//Add item details to db
}, {
//Some error
})
Here is my use case:
I am developing an app that communicates with a server via a REST API and stores the received data in a SQLite database (it's using it as a cache of some sorts).
When the user opens a screen, the following has to occur:
The data is loaded from the DB, if available.
The app call the API to refresh the data.
The result of the API call is persisted to the DB.
The data is reloaded from the DB when the data change notification is intercepted.
This is very similar to the case presented here, but there is a slight difference.
Since I am using SQLBrite, the DB observables don't terminate (because there is a ContentObserver registered there, that pushes new data down the stream), so methods like concat, merge, etc. won't work.
Currently, I have resolved this using the following approach:
Observable.create(subscriber -> {
dbObservable.subscribe(subscriber);
apiObservable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(
(data) -> {
try {
persistData(data);
} catch (Throwable t) {
Exceptions.throwOrReport(t, subscriber);
}
},
(throwable) -> {
Exceptions.throwOrReport(throwable, subscriber);
})
})
It seems like it's working OK, but it just doesn't seem elegant and "correct".
Can you suggest or point me to a resource that explains what's the best way to handle this situation?
The solution to your problem is actually super easy and clean if you change the way of thinking a bit. I am using the exact same data interaction (Retrofit + Sqlbrite) and this solution works perfectly.
What you have to do is to use two separate observable subscriptions, that take care of completely different processes.
Database -> View: This one is used to attach your View (Activity, Fragment or whatever displays your data) to the persisted data in db. You subscribe to it ONCE for created View.
dbObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
displayData(data);
}, throwable -> {
handleError(throwable);
});
API -> Database: The other one to fetch the data from api and persist it in the db. You subscribe to it every time you want to refresh your data in the database.
apiObservable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(data -> {
storeDataInDatabase(data);
}, throwable -> {
handleError(throwable);
});
EDIT:
You don't want to "transform" both observables into one, purely for the reason you've included in your question. Both observables act completely differently.
The observable from Retrofit acts like a Single. It does what it needs to do, and finishes (with onCompleted).
The observable from Sqlbrite is a typical Observable, it will emit something every time a specific table changes. Theoretically it should finish in the future.
Ofc you can work around that difference, but it would lead you far, far away from having a clean and easily readable code.
If you really, really need to expose a single observable, you can just hide the fact that you're actually subscribing to the observable from retrofit when subscribing to your database.
Wrap the Api subscription in a method:
public void fetchRemoteData() {
apiObservable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(data -> {
persistData(data);
}, throwable -> {
handleError(throwable);
});
}
fetchRemoteData on subscription
dbObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(() -> fetchRemoteData())
.subscribe(data -> {
displayData(data);
}, throwable -> {
handleError(throwable);
});
I suggest you really think about all that. Because the fact that you're forcing yourself into the position where you need a single observable, might be restricting you quite badly. I believe that this will be the exact thing that will force you to change your concept in the future, instead of protecting you from the change itself.
I have the following code to fetch a list of items from the internet.
Observable<RealmList<Artist>> popArtists = restInterface.getArtists();
compositeSubscription.add(popArtists.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(artistsObserver));
The trouble is the list has over 80 items and i only want to get the first 5 items. What is the best way to achieve this?
takeis the operator you looking for. (see documentation here : http://reactivex.io/documentation/operators/take.html )
The flatMapIterable transform your RealmList (which implement Iterable, that's why flatMapIterable can be used) to an Observable which emiting all items of your list
Subscription subscription = restInterface.getArtists()
.flatMapIterable(l -> l)
.take(5)
.subscribeOn(Schedulers.io())
.observeOn(androidSchedulers.mainThread())
.subscribe(artistsObserver);
compositeSubscription.add(subscription);
I guess you have no control over server side, so solution is to take first 5 items from received result:
Observable<RealmList<Artist>> popArtists = restInterface.getArtists();
compositeSubscription.add(
popArtists.flatMap(list-> Observable.from(list).limit(5)).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(artistsObserver));