I have an Observable that can emit events in some time after subscribing (e.g. an Observable from the Retrofit). The subscription to this Observable is strictly View-related, so when the View is destroyed I'm unsubscribing from the Observable. I want to perform some actions in doOnNext even if I unsubscribe the Subscription.
Example code:
final Observable<String> observable = ...; // will emit an event in some time in future
final Subscription subscription =
observable.doOnNext(new Action1<String>() {
#Override
public void call(String s) {
//this should be called even if the subscription is unsubscribed
}
}).subscribe();
subscription.unsubscribe();
Is there a way to make sure doOn<something> will be called even if the Subscription is unsubscribed?
EDIT:
Let me give you a bit clearer example:
final Observable<List<GithubRepo>> observable = getGithubReposFromApi();
subscription = observable
.doOnNext(githubRepos -> cacheGithubReposInDb(githubRepos))
.subscribe(githubRepos -> displayGithubReposInCurrentActivity(githubRepos));
And in Activity's onDestroy:
subscription.unsubscribe();
Now... If githubRepos were received after the Activity has been destroyed, the result wouldn't be cached in the database. And I would like it to be.
Okay, so you want the computation to run but want to cut off the Activity-dependent subscriber. You can publish() the sequence and subscribe to it. When unsubscribed, the original sequence will still go on:
ConnectableObservable<T> co = observable
.doOnNext(githubRepos -> cacheGithubReposInDb(githubRepos))
.publish();
Subscription s = co.subscribe(githubRepos ->
displayGithubReposInCurrentActivity(githubRepos));
co.connect();
// ...
s.unsubscribe();
I think you could do one of two things:
1.) Just have two Subscribers: one for View related stuff and one for the other side-effects that you are currently handling in doOnNext. To still have only one upstream subscription you could use:
Observable sharedObservable = observable.replay().refCount();
or maybe just
Observable sharedObservable = observable.share();
// which is the same as observable.publish().refCount();
2.) Use
Observable observableThatWillNeverTrulyUnsubscribe = observable.doOnNext(/* your doOnNext */).publish().autoConnect();
Now, when your Subscriber subscribes to observableThatWillNeverTrulyUnsubscribe it will subscribe to observable and will start emitting items. However, when the Subscriber unsubscribes, it will not unsubscribe upwards and thus doOnNext will continue to receive items.
I think I would prefer to first option as with the second you are giving up any possibility of ever stopping the work of observable.
Related
I have a number of Observables that are used for network requests in my app. Since so much is the same, I apply an Observable transformation to them:
/**
* Creates a transformer that applies the schedulers and error handling for all of the observables in this ViewModel.
*/
private fun applyTransformations(): Observable.Transformer<NetworkState, NetworkState> {
return Observable.Transformer { observable ->
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { NetworkState.Error(it) }
.doOnNext { publishState(it) }
.startWith(NetworkState.Loading())
}
}
The goals I am trying to accomplish with the above:
Apply consistent schedulers
Handle any error by returning an instance of my sealed class.
Handle any onNext by publishing the state returned by the observable.
Start off by sending a Loading state.
This works mostly fine, but what I've noticed is that while I call startWith and a loading state, it is never actually handled by doOnNext(). In other words, publishState() is never called for my loading state.
Where I set up the observables, I don't bother to add a subscriber, because the doOnNext() above is all that I'll need:
val subscription = repository.getInstagramPhotos(count)
.map { mapIGPhotoResponse(it) }
.compose(applyTransformations())
.subscribe()
If I were to supply a subscriber above, though, it would handle the loading state. It would also handle two onNext() calls - one for the subscriber supplied, and one for the doOnNext in the transform.
Is there a way to modify this startWith call to emit to whatever I've specified in doOnNext? I'm using RxJava 1.
Edit: Just to clarify some more, if I track what's emitted I expect to see two things. Loading -> Success. What I actually see is just Success. If I supply a subscriber to the observable I see Loading -> Success -> Success.
startWith should be before doOnNext.
Rxjava methods, though they look like they use the builder pattern, actually don't. They return a new observable each time an operator is applied. In your case, your doOnNext observable completes before your start with observable, so it's consumer isn't called with what you supply in startWith.
Ideally, you should go with:
observable
.startWith(NetworkState.Loading())
.doOnNext { publishState(it) }
.onErrorReturn { NetworkState.Error(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
Also, be careful with subscribing with no Consumer for onEror should it happen. Since you have nothing to consume the onError, RxJava will crash your app since it has nothing to notify for the error. Consider replacing the doOnNext with a Success Consumer in subscribe, and an empty Consumer for the error if you want to ignore it.
Also doOnNext is typically used for side effects, such as logging and the sort, they're more of a convenience than true functional operators.
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.
When I receive a push notification, I add the notification payload to my DB in the following way:
personObject.insertObjectIntoDb(searchResult, value, oneOnOneChannel).observeOn(Schedulers.computation()).subscribe(insertSinglePersonSubscriber);
I have a Subscriber instantiated in the onCreate() of my activity.
insertSub = new Subscriber<Long>() {
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted: insertSub complete");
}
#Override
public void onError(Throwable e) {
Log.d(TAG, "onError: insertSub error");
}
#Override
public void onNext(Long aLong) {
Log.d(TAG, "onNext: insertSub next");
}
};
On the first invocation of personObject.insertObjectIntoDb(), the onComplete() of insertSub is called, just as expected.
This would mean the now the subscription has ended and the subscriber gets unsubscribed.
However, I don't want to stop the subscription and want the insertSub callbacks to be called every time there's a new push notification.
I've been reading about share() for one observable and multiple subscribers but that's not what I want. I read about replay() but it the observable never stops emitting (oops). Changing Subscriber to Observer also didn't help and on more thorough reading I found that Subscriber inherits from Observer and the same rules apply (apart from the fact the with a subscriber we need to unsubscribe.
I am not sure if the observer observable stops emitting (after emitting once). How do I make the observable emit multiple times, whenever there's a new notification?
Also, what's the best way to re-establish the subscription?
First of all, according your description it seems that you don't have some basic concepts completely figured out.
Observers don't emit but rather receive emissions.
Singles can't emit more than once. They were designed to emit only one event and complete. If you don't want this kind of behavior, you need to use some other Observable type.
But Single's are not a problem here. To compose a stream that behaves like this, you need to think one level above. If you design a stream that receives signals from push notifications, you can react to each of them by subscribing to your Single and forward its emission back to the main stream of notifications. That way, you funnel all your emissions into one Observer, just like you described. This can be easily achieved with flatMap operator.
notificationStream
.flatMap(notificationPayload ->
personObject
.insertObjectIntoDb(/* Extract arguments from payload */)
.subscribeOn(Schedulers.computation())
)
.subscribe(insertSinglePersonSubscriber)
notificationStream can be created either by using some library designed for it ( i.e. https://android-arsenal.com/details/1/3546 ), writing it yourself or using a Subject. The easiest way is definitely third one, although not cleanest. You just create a subject and call its onNext method right in the place where you subscribe to insert object Single right now.
When composing the stream to insert a value, I changed your observeOn operator to subscribeOn as I guess that you don't completely understand what each of those operators do.
The observeOn operator switches the scheduler on which are emissions handled from that point of the stream.
On the other hand, subscribeOn operator instructs observable to produce items on a particular scheduler. As I hope I can assume that you do the "heavy lifting" in the producing the emission in the Single itself -- that would be the insert itself -- you need to subscribe on a computation scheduler, not observe on it. If I am wrong about this and you need to do computation work in the observer, then add observeOn operator after flatMap on the notification stream.
RxJava is awesome, keep learning. But theoretical knowledge is important :-)
What about use relay. Relay is a subject except without the ability to call onComplete or onError
https://github.com/JakeWharton/RxRelay
Here you can see a practical example
https://github.com/politrons/reactive/blob/master/src/test/java/rx/relay/Relay.java
Answering this as I found a solution to the overall problem.
Basically, I wanted to make a notificationStream as #koperko mentioned in his answer. But he suggested creating a Subject (PublishSubject if I am not wrong).
But that solved only half of my problem. What I wanted to do was to take have a notification Stream that adds the notification to my DB and after it's inserted, update the UI by fetching the last inserted notification.
personObject.insertObjectIntoDb(person)
.observeOn(Schedulers.computation())
.switchMap(new Func1<Long, Observable<PersonObject>>() {
#Override
public Observable<PersonObject> call(Long aLong) {
Log.d(TAG, "call: inserted into DB with ID " + aLong);
Log.d(TAG, "call: Now fetching this item from the DB");
return personObject.getPersonById(aLong).observeOn(Schedulers.computation());
}
}).subscribe(getSinglePersonFromDBSubscriber);
This not only saved me from having to create a Subject, but also saved me from worrying about trying to make Single's emit more than once, which as #koperko mentioned was incorrect. switchMap() was what I was looking for.
I'm having trouble trying to use a BehaviorSubject (via RxRelay.BehaviorRelay) to store the most recent emission from a continuous Observable.
By 'continuous', I mean the source Observable is designed to emit data whenever it's underlying dataset changes.
The BehaviorSubject is subscribed to the source Observable.
It appears that once I subscribe to the BehaviorSubject, I only ever seem to receive the first value emitted from the source Observable to the BehaviorSubject. The source Observable appears to no longer emit continuously, and in fact, ceases to emit any more items.
So, here's a somewhat contrived example:
//A Singleton
public class DataManager {
private Observable<List<Item>> itemsObservable;
//A BehaviorRelay (BehaviorSubject)
public BehaviorRelay<List<Item>> itemsRelay = BehaviorRelay.create();
private DataManager() {
//An Observable which emits when subscribed, and then subsequently when the underlying uri's data changes
itemsObservable = SqlBrite.createQuery(Uri uri, ...);
//In practice I would lazily subscribe to the relay.
itemsObservable.subscribe(itemsRelay);
}
}
Now, subscribing to the BehaviorSubject from somewhere:
// Subscribe to the BehaviorSubject
DataManager.getInstance.itemsObservable.subscribe(items -> {
//Here, I would expect 'items' to be the most recent List<Item> emitted from the source Observable to the BehaviorSubject.
//However, it seems like it's only ever the *first* item emitted from the source Observable to the BehaviorSubject.
//It seems like the source Observable never emits to the BehaviorSubject more than once, despite the source's underlying
//dataset having changed (I am triggering this change in testing).
});
It turns out there was just a flaw in my testing. The uri backing the SqlBrite Observable wasn't being notified of changes, so the Observable wasn't emitting any new values. There was also a bit of a red herring to do with the Subject being re-subscribed in an onResume lifecycle method.. All working as intended.
I need an Observable that never ends, and just process some data and chain another observable when there are items on a list. Is there any way of accomplish that, and what would be the best approach=?
My closest idea was to create a timer observable and check every x seconds if there are items on the list. This idea is not ideal, because i need to process the data as soon as there are values on that list, which i modify outside the observable chain.
return Observable.timer(2, TimeUnit.SECONDS)
.flatMap(integer -> captureList.getLatestCaptureCut())
.flatMap(vp::processVideo)
.observeOn(AndroidSchedulers.mainThread())
.repeat()
I think you can use Subject, and push your next items there.
PublishSubject<Integer> subject = PublishSubject.<Integer>create();
subject.flatMap(integer -> captureList.getLatestCaptureCut())
.flatMap(vp::processVideo)
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
//push new items
subject.onNext(0);
subject.onNext(1);
I would suggest a PublishSubject in your CaptureList class. Instead of providing a pull method getLatestCaptureCut(), you could provide a push method, with a Subject:
PublishSubject<VP> captured = PublishSubject.create();
You could then .subscribe() to the PublishSubject and process the data when they come in.
In your CaptureList you would call
captured.onNext(vp);
every time new data is available. For instance, in your setLatestCaptureCut(). I'm assuming you already have some kind of routine that generates the CaptureCut and store it, to make it available in getLatestCaptureCut().