RxJava: How to prepend startWith() default emit EVERY TIME parent observable emits? - java

im trying to have a pattern, where my observable which produces some object, is transformed into domain events like Started, Success, Error emited around the observable producing, if that makes sense
public Observable<BookRenderingEvent> extractAndRenderObservable(String epubPath) {
return extractObservable(epubPath)
.flatMapObservable(extractedEpub -> renderObservable(extractedEpub)
.<BookRenderingEvent>map(renderedEpub -> new BookRenderingEvent.Success(renderedEpub))
.onErrorReturn(t -> new BookRenderingEvent.Error())
.startWith(new BookRenderingEvent.Started()));
}
private Observable<RenderedEpub> renderObservable(ExtractedEpub extractedEpub) {
return Observable.combineLatest(readerConfigObservable(), pagerDimensionsObservable(), ..)
.switchMapSingle(foo -> doRenderObservable()) <--- heavy work
.map(bar -> new RenderedEpub(bar))
}
renderObservable contains a heavy action so I want to emit these state events, so UI can react accordingly (with success containing the extractedEpub object as you can see in the map)
What my problem is that, renderObservable contains combineLatest(), so it "stays open" and emit mutiple times in time, whenever its obervables emit.
So the flow of events is Started, Success, Succes ... Success.
I want it to be Started, Success, Started, Success .. etc. i.e prepend Started event whever combineLatest emits, but my rx knowledge is insufficient.
Thanks

You could insert the following into the observable chain at the right place:
.flatMap( event -> Observable.just( new BookRenderingEvent.Started(), event )
This will emit the Started event before every event that it receives.
Of course, you could add in some logic so that you don't issue Started if the event is Started, etc.

Ok Ive managed to figure it out. The key info I was missing is that right side of flatmap gets subscribed when left side emits. Therefore the startWith had to be moved to the right side of flatmap observable, that gets subscribed to when ever combineLatest emits
public Observable<BookRenderingEvent> extractAndRenderObservable(String epubPath) {
return extractObservable(epubPath)
.flatMap(extractedEpub -> Observable.combineLatest(readerConfigObservable(), pagerDimensionsObservable(), ..)
.switchMap(foo -> renderObservable(extractedEpub)
.<BookRenderingEvent>map(renderedEpub -> new BookRenderingEvent.Success(renderedEpub))
.onErrorReturn(t -> new BookRenderingEvent.Error())
.startWith(new BookRenderingEvent.Started()));
}

Related

getReactions() returns empty list, regardless of the amount of reactions

When I try to read the reactions added to a message sent I'm always getting an empty list.
MessageBuilder mb = new MessageBuilder();
channel.sendMessage(mb.build()).queue((t);
After adding reactions to it, I execute this code:
System.out.println(t.getReactions().size());
for (MessageReaction r : t.getReactions()) {
System.out.println(r.getReactionEmote().getName());
}
(this is yet to be implemented, I'm just trying to get to understand how I can use it)
I am expecting the output to be the amount of reactions I added, yet System.out.println(t.getReactions().size()); will always print 0 regardless of the amount of reactions added to the message sent.
Furthermore, when iterating of the list containing reactions, it always won't print anything to the console, since the list seems to be empty.
Is there something I need to add that I can use .getReactions()?
What I also tried is using an eventWaiter:
eventWaiter.waitForEvent(GuildMessageReactionAddEvent.class, (event) -> {
return "🎵".equals(event.getReactionEmote().getName()) && !event.getUser().isBot()
&& event.getMessageIdLong() == messageId;
}, (event) -> {
System.out.println("Reacting to reaction");
}, (long) 30, TimeUnit.SECONDS, () -> {
System.out.println("Timeout. No event was registered.");
});
This always outputs the timeout warning, regardless of reactions added.
This is not how JDA works.
The reactions do not get magically updated, you can get the reactions by fetching the message or by listening for the reaction add event.
To use the event waiter properly you must make sure that the same instance is also registered on JDA by using JDABuilder#addEventListeners.
So either listen for the event manually or make sure that you only use once instance of the EventWaiter class in your code that is also registered on the JDA builder.

Server sent events being multiplicated when incoming to React

I have a simple Java Spring application that uses server sent events to send data to a frontend React app. Here is the how the backend is written:
#GetMapping(value = "/plot", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux get() {
return Flux.interval(Duration.ofMillis(1000))
.map(interval ->
currentStateMapper.map(eventRepository.getAll()).stream()
.map(plotDataMapper::map)
.collect(Collectors.toList())
);
}
What this does is probably less important but the idea is that I store recent events and the repository feeds me all of the events stored (they are stored in an infinispan cache), and then i use some mappers to get the current state and then to map it to objects suitable for visualizing with a XY plot.
It should send a message with the plotting data to the frontend every second. And when I open the endpoint with my browser (Chrome) it works like a charm. can see a new JSON appearing every second. But when I use React's event source to receive the messages instead of 1 message each second I get 6 identical messages each second. Here is the frontend implementation:
const Component = () => {
const plotDataEventSource = new EventSource(
"http://localhost:8080/tags/plot/antenna"
);
useEffect(() => {
plotDataEventSource.onmessage = e => {
console.error(e)
setData(prevState => {
/* some data handling */
return newState;
});
};
});
return ( /* html with plot */ );
}
And so the console.error() gets logged 6 times each second, identical objects. What can be the reason of this behavior?
useEffect will trigger the callback on each render - if you want it to behave like componentDidMount you should pass an empty array as the second argument.
The second argument is an array of values (usually props):
If any of the value in the array changes, the callback will be fired
after every render.
When it's not present, the callback will always be fired after every
render.
When it's an empty list, the callback will only be fired once,
similar to componentDidMount.
It is missing some important parts of your code, but the re-render may be happening because you didn't inform useEffect under what conditions it should run, and it can be achieved by a second parameter in the useEffect method.
The way you wrote it, useEffect is triggered every time your component renders. If you make a small change, passing a condition to useEffect it will run only when that condition change. For instance:
useEffect(() => {
plotDataEventSource.onmessage = e => {
console.error(e)
setData(prevState => {
/* some data handling */
return newState;
});
};
}, [data]); // <---- Assuming you are changing something called data.
When your component is rendering, useEffect will compare the value of data and will run only if it has changed.

Store last subscriptions of consumers in hashmap

I have Rx Stream where I'm using a grouped observable to have multiple observable according to a discriminant value. The thing is that I want to keep in memory the last subscription of each group observable in order to delete the stream when a new one is created (unsubscribe)
I'm using a simple Hashmap to store value according to a key which is an observable it self but it doesn't to worth it.
Here is where I am so far:
stream
.groupBy(ts -> ts.getLogin())
.map(receptor -> receptor.asObservable())
.forEach(consumer -> {
controlConsumer(consumer);
});
public void controlConsumer(Observable<Number> pConsumer) {
Subscription mySubscription = pConsumer
.buffer(2,1)
.subscribe();
// Remove the current consumer last subscription if existing
if(consumersLastSubcriptions.containsKey(pConsumer)) {
LOG.info("removing last subscription");
Subscription lLastSubscription = consumersLastSubcriptions.get(pConsumer);
lLastSubscription.unsubscribe();
}
// store the new consumer subscription
consumersLastSubcriptions.put(pConsumer, lSubscription);
}
UPDATE
Actually i might have different items defined by the login of the user emitting them. What i want is to store the last subscription of each user in order to remove it when i need to recreate a new one when needed. My purpose is to shutdown every stream no more used and don't let them open for nothing.

Combine Swing event observables with other observables

I have a label which displays error messages. If you double click on it you get a big dialog showing the whole stack trace. I have two observables: One for the errors and one for the click events:
final ConnectableObservable<Notification> errorNotifications = pm
.getNotificationObservable()
.filter(notification -> notification.getType().isError() && !notification.getLongMessage().isEmpty())
.replay(1);
errorNotifications.connect();
SwingObservable.fromMouseEvents(dialog.getMessagePanel().getMessageLabel())
.map(MouseEvent::getClickCount)
.filter(number -> number >= 2)
.subscribe(integer -> errorNotifications
.take(1)
.subscribe(notification -> ErrorDialog.showError(dialog.getFrame(), "Error", notification.getLongMessage())));
I filter the notification observable to only show erros and replay the last error if I subscribe from it from inside my click observable.
Now my question is, are there any operators in RxJava by which I can do this more... neatly? I tried to use combineLatest() but this had the effect, that every time an error ocured the dialog would open.
In a more abstract way: I have two observables, one is like the "master": If the master observable (click observable) emits an item, the other observable (my error notifications) should emit the latest item.
Using another Observable in a subscription is often a design flaw.
You may check the flatMap operator in this response. It will help you to emits error notification when you emit another event.
For example, if you want to use flatMap operator with your code, it can be updated like this :
final ConnectableObservable<Notification> errorNotifications =
pm.getNotificationObservable()
.filter(notification -> notification.getType().isError() && !notification.getLongMessage().isEmpty())
.replay(1);
errorNotifications.connect();
SwingObservable.fromMouseEvents(dialog.getMessagePanel().getMessageLabel())
.map(MouseEvent::getClickCount)
.filter(number -> number >= 2)
.flatMap(integer -> errorNotifications.take(1))
.subscribe(notification -> ErrorDialog.showError(dialog.getFrame(), "Error", notification.getLongMessage())));

using toList operator on a neverending Observable

The "neverending" Observable emits a parameter item as soon as the user changes something on the ui.
depending on this item I need to do a request. as soon as the parameter item changes the request should be stopped and a new one started.
parameterObservable
.switchMap(this::search) // to stop an restart the request with new params
.toList()
.subscribe(resultList -> {/* do something*/});
The Problem here is that, the toList Operator waits for the parameterObservable to complete. Which will not happen.
To make the toList work I could do something like this:
parameterObservable
.subscribe(params -> search(params).toList()
.subscribe(/* do something */)
);
But then the switchMap is missing.
How can I achieve this?
How about this:
parameterObservable
.switchMap(params -> search(params).toList())
.subscribe(resultList -> {/* do something*/});

Categories