Query RxJava2 Db into another subject - android

On my Android project I'm heavily relying on RxJava2, SqlBrite(with RxJavaInterop) and SqlDelight.
I got one rx stream that is supposed to go indefinitely (until my service stops) and on it I have a .flatMap of Function<String, ObservableSource<Action>>.
Meaning, this flatMap contains a Subject<Action>, will receive String actionId, do some (irrelevant for the question) processing on those actionId, and depending on condition should query the database for the Action object and dispatch it to the subject
My first approach was to do the query directly:
Cursor c = db.query(...);
if(c.moveFirst()) {
Action a = Action.SELECT_ALL_MAPPER.map(c);
subject.onNext(selectAll);
}
But this blocks the running thread and I rather trigger this on its own stream that should do the following:
query (should return 0 or 1 item)
if there's a value: map to Action object and push the value to the subject
if there's no value: terminate/dispose.
the subject cannot receive terminate or error. It must stay alive for future events.
My current approach is the following code:
RxJavaInterop.toV2Observable(db.createQuery(
Action.TABLE_NAME,
Action.FACTORY.Select_by_id(actionId).statement)
.mapToOne(new Func1<Cursor, Action>() {
#Override public Action call(Cursor cursor) {
return Action.SELECT_ALL_MAPPER.map(cursor);
}
}))
.take(1)
.subscribe(new Consumer<Action>() {
#Override public void accept(Action action) throws Exception {
subject.onNext(action);
}
});
And although this seems to do the trick on the first impression, I see a few errors on it:
I can't dispose it. Even if I get the reference to Disposable object, I cannot call it from inside Consumer<Action> because it "might have not been initialised" (which I understand the reason, it's OK).
if there's no action with the given ID, the observable will hang in there for ever, until the VM is killed.
So the question:
How can I do that?

I rather trigger this on its own stream
Take a look at RxAndroid. This could look like:
yourRxStream
.flatMap(*db request here*)
.subscribeOn(Schedulers.io())
.subcribe(subject);
the subject cannot receive terminate or error. It must stay alive for
future events.
Switch the Subject with a Relay:
Subjects are useful to bridge the gap between non-Rx APIs. However,
they are stateful in a damaging way: when they receive an onComplete
or onError they no longer become usable for moving data. This is the
observable contract and sometimes it is the desired behavior. Most
times it is not.
Relays are simply Subjects without the aforementioned property. They
allow you to bridge non-Rx APIs into Rx easily, and without the worry
of accidentally triggering a terminal state.
Finally for the request than could output 0 or 1 item, use a Maybe.

Related

Is it possible to implement an operator like delay but that also delays errors?

I'm trying for some time now to implement an extension function (just becuse it's easier to me) that is capable of delaying both normal item emissions and errors. The existing delay operators only delays normal item emissions, errors are delivered ASAP.
For context, I'm trying to immitate an Android LiveData's behavior (kinda). LiveDatas are a observable pattern implementation that is lifecycle aware. Their observers are only notified if they are in a state where they can process that emission. If they are not ready, the emission is cached in the livedata and delivered as soon as they become ready.
I created a BehaviourSubject that emits the state of my Activities and Fragments when it changes. With that I created a delay operator like this:
fun <T> Flowable<T>.delayUntilActive(): Flowable<T> = delay { lifecycleSubject.toFlowable(BackpressureStrategy.LATEST).filter { it.isActive } }
and then use it like this
myUseCase.getFlowable(Unit)
.map { it.map { it.toDisplayModel() } }
.delayUntilActive()
.subscribe({
view.displaySomethings(
}, { }).addTo(disposables)
So even if myUseCase emits when the view is not ready to display somethings, the emission won't reach onNext() until the view does become ready. The problem is that I also want the view to displayError() when onError is triggered, but that too is lifecycle sensitive. If the view isn't ready, the app will crash.
So I'm looking for a way to delay both emissions and errors (onComplete would be good too). Is this possible?
I tried some things with zip, onErrorReturn, delay inside delay, but nothing seemed right. I'd be equally unimpressed if this had a really easy solution I'm overlooking, or is impossible. Any ideas are welcome.
Bonus: any better way to do that for Single and Completable too? currently I'm just converting them to flowable.
Thanks in advance!
You can handle the error via onErrorResumeNext, then taking the same error and delaying it via delaySubscription until your desired signal to emit said error happens:
source
.onErrorResumeNext({ error ->
Observable.error(error)
.delaySubscription(lifecycleSubject.filter { it.Active } )
})

How can I know when I need to dispose of a disposable in RxJava?

Trying to understand RxJava here. We have this code:
public void notifyNewOwnersSynced() {
OrgTreeType orgTreeType = getOrgTreeType();
new OrgTreeQuerier().queryOrgUserIds().byOrgTreeType(orgTreeType).executeAsync()
.map(opt -> opt.isPresent() ? opt.get() : new HashSet<String>(0))
.subscribe(
this::onNewOrgUserIds,
e -> Log.exception(new Exception("Unable to update selected id filter for type " + orgTreeType, e))
);
}
private void onNewOrgUserIds(#NonNull Set<String> allIds) {
synchronized (mLock) {
for (String id : allIds) {
if (!mPreviousAllIds.contains(id)) {
mSelectedIDs.add(id);
}
}
Set<String> idsNoLongerInHierarchy = new HashSet<>(); //Because we can't remove while we are iterating.
for (String selectedId : mSelectedIDs) {
//If there is a selected ID not in the new hierarchy...
if (!allIds.contains(selectedId)) {
//Plan to remove it.
idsNoLongerInHierarchy.add(selectedId);
}
}
mSelectedIDs.removeAll(idsNoLongerInHierarchy);
mPreviousAllIds = allIds;
}
mSaveListener.saveChangesAndPostFilterChangedEvent();
postSelectedIdsChangedEvent();
}
We have two lint warnings showing up on it: The result of subscribe is not used. and Result of single.subscribe() is ignored
This is in a class that is used by our UI(Fragments) to keep track of what users have been selected.
But on a larger scale we have a lot of spots in our code that use Rx like this to do something in the background (map something, network call, save data to the DB) and we don't every use the result.
Can I safely suppress these errors? or do I need to add handling for the Disposables?
How can I know when I need to dispose of a disposable?
Although not a comprehensive list of cases, I think I can point out some that I've faced during my career and might help you out.
The most common scenario I faced was when we make network calls that take too long and the app is put in the background. If not disposed, the result of the network will be forwarded to the subscriber. This is not really the issue. The problem is that usually the subscriber wants to change something UI related, which crashes the app. In this case, you dispose because you are no longer interested in receiving these events.
There are cases where the way the subscriber handles the result wouldn't be problematic, but the IDE has no way to know this and hence it warns you all the time.
I'm sure there are tons of other reasons why disposing should be handled - i.e., when observables acquire resources when subscribed and release them once unsubscribed from. So in general I guess it's good to handle the disposables. I only ever kept a disposable undisposed when I wanted to keep downloading files in the background and to be honest with you, I'm not even sure if this is a good practise.
Adding to this, if an observable or any of the other flavored observables (single, maybe, etc.) terminates, then it's disposed automatically.

Batching in Android with RxJava window or buffer?

I'm looking to implement a batching mechanism before an api post for some simple event collection and logging.
Since this is Android, I also would like to handle lifecycle events for if this service is stopped, so what is the way to manually flush the buffered window if the service is stopped but the count or time has not been hit yet.
For example, I have a PublishSubject (subject), create a flowable and the perform a window operation on it like so:
subject.toFlowable(BackpressureStrategy.BUFFER)
.window(30,
TimeUnit.SECONDS,
20,
true)
.flatMapSingle { it.toList() }
.subscribe (this::send)
If my service/app is paused or killed, I'd like to just send what is in the buffer.
The problem you face is to stop observing when necessary and flush current items in window. Documentation for Flowable.window() operator say this:
When the source Publisher completes or encounters an error, the resulting Publisher emits the current window and propagates the notification from the source Publisher.
So you need to make your Subject emit error or complete. In most of the cases, this is not a correct way how to work with subjects. Let's replace Subject with something what can be easily completed:
private val stopObserver = BehaviorSubject.create<Unit>() // (1)
private fun emitStop() { // (2)
stopObserver.onNext(Unit)
}
private fun sourceSubject(): Flowable<Long> { // (3)
return Flowable.interval(1, TimeUnit.SECONDS)
.takeUntil(stopObserver.toFlowable(BackpressureStrategy.BUFFER)) // (4)
}
private fun runObservation() { // (5)
sourceSubject()
.window(10)
.flatMapSingle { it.toList() }
.doOnNext { Log.d("onNext", "${it.count()} items") }
.subscribe()
}
Explanation of important parts:
Create new Subject which emits everytime you realize app to being stopped or paused.
You can simply emit onNext event to Subject when needed with function emitStop()
sourceSubject() function imitates your source Subject. This one emits item every second.
takeUntil() operator completes stream when passed Publisher (stopObserver) emits an item. This ensures, our overall source Publisher (sourceSubject) completes.
I have used simpler version of window() operator, but all of them use the same principle regarding to source publisher.
Possible output:
2019-11-30 10:48:54.527 D/onNext: 10 items
2019-11-30 10:49:04.524 D/onNext: 10 items
2019-11-30 10:49:14.525 D/onNext: 10 items
2019-11-30 10:49:19.056 D/onNext: 4 items

Make Single Observable emit more than once

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.

Replace listeners with RxJava

Currently I am investigating a migration to RxJava and decided that a manager of mine(accountManager) would be an interesting place to start. Currently the Manager has a list of listeners and sends updates accordingly, both when the account gets updated and when something goes wrong.
private List<WeakReference<ProfileChangeListener>> mListeners = new ArrayList<>();
public interface ProfileChangeListener {
void onProfileUpdated(Account account);
void onProfileFailed(Exception e);
}
My Rx solution involves a Subject
private SerializedSubject<Account, Account> mManagerSubject = new SerializedSubject<>(BehaviorSubject.<Account>create());
public Observable<Account> observe() {
return mManagerSubject;
}
and then when an update happens I call one of the following:
private void onProfileUpdated(Account account) {
mManagerSubject.onNext(account);
}
private void onProfileFailed(final Exception e) {
mManagerSubject.onError(e);
}
Issue
The Issue is that once onError is called anyone listening via observe will never get another update from onNext.
I still want the subscribers to receive onError so they can handle the error state but at a later time onNext could still be called with an updated account and I still want the subscribers to handle the updated account.
I've tried solutions using onErrorResumeNext, onErrorReturn onExceptionResumeNext but none of them propagate the onError.
TLDR: How do I keep the subscribers subscribed after onError is called while still propagating onError?
"Errors" in Rx can be a a little difficult to grasp at first, because they have a slightly different meaning from what most people expect.
From the Error Handling documentation (emphasis mine):
An Observable typically does not throw exceptions. Instead it notifies any observers that an unrecoverable error has occurred by terminating the Observable sequence with an onError notification.
onError() is supposed to be used when an Observable encounters an unrecoverable error- that is when your Observable cannot continue emitting items. When you are subscribing, you might use something like onErrorResumeNext to try some recovery action, but that should be the end of the source Observable.
Instead, you may want to adjust what your Observable emits to support emitting an error item, or include a flag indicating that an error was encountered.
If your error truly is unrecoverable, then you may want to revisit your recovery strategy and try a slightly different approach.

Categories

Resources