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.
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.
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.
In my Android app I am using domain level Repository interface, which is backed with local DB implemented using SqlBrite and network api with Retrofit observables. So I have method getDomains(): Observable<List<Domain>> in Repository and two corresponding methods in my Retrofit and SqlBrite.
I don't want to concatenate or merge, or amb these two observables. I want my Repository to take data only from SqlBrite and since SqlBrite returns QueryObservable, which triggers onNext() every time underlying data changed, I can run my network request independently and store results to SqlBrite and have my Observable updated with fetched from network and stored to DB data.
So I tried to implement my Repository's getDomains() method as follow:
fun getDomains(): Observable<List<Domain>> {
return db.getDomains()
.doOnSubscribe {
networkClient.getDomains()
.doOnNext { db.putDomains(it) }
.onErrorReturn{ emptyList() }
.subscribe()
}
}
But in this case every time the client should subscribe, every time it would make network requests, that is not so good. I thought about other do... operators to move requests there, but doOnCompleted() in case of QueryObservable would never be called, until I call toBlocking() somewhere, which I won't, doOnEach() also not good as it makes requests every time item from db extracted.
I also tried to use replay() operator, but though the Observable cached in this case, the subscription happens and results in network requests.
So, how can combine these two Observables in the desired way?
Ok, it depends on the concrete use case you have: i.e. assuming you want to display the latest data from your local database and from time to time update the database by doing a network request in the background.
Maybe there is a better way, but maybe you could do something like this
fun <T> createDataAwareObservable(databaseQuery: Observable<T>): Observable<T> =
stateDeterminer.getState().flatMap {
when (it) {
State.UP_TO_DATE -> databaseQuery // Nothing to do, data is up to date so observable can be returned directly
State.NO_DATA ->
networkClient.getDomains() // no data so first do the network call
.flatMap { db.save(it) } // save network call result in database
.flatMap { databaseQuery } // continue with original observable
State.SYNC_IN_BACKGROUND -> {
// Execute sync in background
networkClient.getDomains()
.flatMap { db.save(it) }
.observeOn(backgroundSyncScheduler)
.subscribeOn(backgroundSyncScheduler)
.subscribe({}, { Timber.e(it, "Error when starting background sync") }, {})
// Continue with original observable in parallel, network call will then update database and thanks to sqlbrite databaseQuery will be update automatically
databaseQuery
}
}
}
So at the end you create your SQLBrite Observable (QueryObservable) and pass it into the createDataAwareObservable() function. Than it will ensure that it loads the data from network if no data is here, otherwise it will check if the data should be updated in background (will save it into database, which then will update the SQLBrite QueryObservable automatically) or if the data is up to date.
Basically you can use it like this:
createDataAwareObservable( db.getAllDomains() ).subscribe(...)
So for you as user of this createDataAwareObservable() you always get the same type Observable<T> back as you pass in as parameter. So essentially it seems that you were always subscribing to db.getAllDomains() ...
if your problem is that you have to subscribe your observer every time that you want to get data you can use relay, which never unsubscribe the observers because does not implement onComplete
/**
* Relay is just an observable which subscribe an observer, but it wont unsubscribe once emit the items. So the pipeline keep open
* It should return 1,2,3,4,5 for first observer and just 3, 4, 5 fot the second observer since default relay emit last emitted item,
* and all the next items passed to the pipeline.
*/
#Test
public void testRelay() throws InterruptedException {
BehaviorRelay<String> relay = BehaviorRelay.create("default");
relay.subscribe(result -> System.out.println("Observer1:" + result));
relay.call("1");
relay.call("2");
relay.call("3");
relay.subscribe(result -> System.out.println("Observer2:" + result));
relay.call("4");
relay.call("5");
}
Another examples here https://github.com/politrons/reactive/blob/master/src/test/java/rx/relay/Relay.java
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.
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().