I am looking for a way, hopefully using RxJava for consistency, to monitor the progress of multiple subscribers that may be fired at different times. I am aware of how to merge or flatMap subscribers together when they are all fired from one method but I am unaware of a way to do it when they are fired at different times from different methods.
For example, if I have 2 long running tasks attached to button presses. I push button 1 and fire off the observable/subscriber, half way through running I push button 2 to fire off the second observable/subscriber.
I want to enable a button when no tasks are running and disable it when one or more tasks are running.
Is this possible? I am trying to avoid setting instance variable flags as well.
I would use a separate BehaviorSubject and scan to monitor execution status. This is quite similar to an instance variable, but probably it can inspire you to a better solution. Something like this:
private final BehaviorSubject<Integer> mProgressSubject = BehaviorSubject.create(0);
public Observable<String> firstLongRunningOperations() {
return Observable.just("First")
.doOnSubscribe(() -> mProgressSubject.onNext(1))
.finallyDo(() -> mProgressSubject.onNext(-1)));
}
public Observable<String> secondLongRunningOperations() {
return Observable.just("Second")
.doOnSubscribe(() -> mProgressSubject.onNext(1))
.finallyDo(() -> mProgressSubject.onNext(-1));
}
public Observable<Boolean> isOperationInProgress() {
return mProgressSubject.asObservable()
.scan((sum, item) -> sum + item)
.map(sum -> sum > 0);
}
Usage will be like this:
isOperationInProgress()
.subscribe(inProgress -> {
if (inProgress) {
//disable controls
} else {
//enable controls
}
});
With this approach you can have any number of long running operation and you do not have to fire them all. Just don't forget to call doOnSubscribe and finallyDo.
PS. Sorry, I didn't test it, but it should work.
To make this possible, let both long running operations emit an onNext event on a PublishSubject. Combine both Subjects with a zip or combineLatest function and subscribe to this. Once the combine function receives an event, this means that both Subjects have emitted an onNext event, thus both long running operations have finished and you can enable the 3rd button.
private PublishSubject<Boolean> firstSubject = PublishSubject.create();
private PublishSubject<Boolean> secondSubject = PublishSubject.create();
#Override
public void onStart() {
super.onStart();
subscribeToResult();
}
private Observable<Integer> firstOperation() {
return Observable.just(100)
.delay(1000) // takes a while
.subscribe(tick -> firstSubject.onNext(true));
}
private Observable<Integer> firstOperation() {
return Observable.just(200)
.delay(1000) // takes a while
.subscribe(tick -> secondSubject.onNext(true));
}
private void subscribeToResult() {
Observable.zip(
firstSubject,
secondSubject,
(firstResult, secondResult) -> return true
).subscribe(
tick -> thirdButton.setEnabled(true)
)
}
Definitely take a look at the RxJava combine functions.
Related
I am trying to implement the RxJava event bus, where my use case is to get triggered when subscribed and when the event is sent. But with my code even when I send the event once, I am receiving multiple events. It is working fine for the first time, it is behaving weirdly from the second time I login into my application. i.e; For the first time desired code implemented once, for the second time it implemented two time and so on.
public class RxBus {
public RxBus() {
}
private PublishSubject<String> bus = PublishSubject.create();
public void send(String str) {
bus.onNext(str);
}
public Observable<String> toObservable() {
return bus;
}
}
The code to subscribe RxBus is below:
public void sendEvents(){
rxBus.send("Trigger event");
}
public void startListener(){
rxBus.toObservable().subscribe(str -> {
//do action//This is executing multiple lines
});
}
In the above code, even though when the sendEvents() is executed once the line containing "do action" is executing multiple times. So, is something I am doing wrong here. When I went through some blogs they are asking to unsubscribe the subscription when we visit that screen a second time. But how can I unsubscribe from that?
Help here is greatly appreciated!
Easy solution is to declare a field:
SerialDisposable busDisposable = new SerialDisposable();
Modify you startListener method:
public void startListener() {
busDisposable.set(rxBus.toObservable().subscribe(str -> {
// ...
}));
}
In that way, when you add new subscription the previous one will be disposed, so you will end up with only one subcription at a time. This is good if your startListener call is not determined by the lifecycle. (Remember to call busDisposable.dispose() when you no longer want to recieve events. )
But if you call your startListener in onResume/onStart/onCreate, you should better use Disposable instead of SerialDisposable and simply call stopListener method in onPause/onStop/onDestroy.
public void stopListener() {
busDisposable.dispose();
}
I'm having an issue trying to understand, in a reactive way, how simultaneous operations to the same observable should work.
The scenario is the following:
I have a list of users and a remove button.
Every time I press remove I'm making a call to the API: UsersApi.removeUser. It is possible to remove multiple users at the same time. Which means that multiple UsersApi.removeUser are happening simultaneously.
After each UsersApi.removeUser I need to make a UsersApi.refreshUser call
So in terms of pseudo code what I am doing when clicking remove is the following:
Presenter:
public Observable<User> removeUser(int userId) {
return UsersApi.removeUser(userId)
.flatMap(user -> UsersApi.refreshUser(userId));
}
Fragment:
public void removeUser() {
presenter.removeUser(userId)
.subscribe(user -> {
//remove user from ui
// update number of total users
})
}
The problem with this approach is that because of the asynchronous nature of the remove (multiple removes allowed) I cannot guarantee that what is reaching the subscribe is the latest one. The subscribe will be reached twice, one for each remove, and the user info might not be updated or the latest. Does that make sense?
What I want to happen:
Parallel/Simultaneous remove calls using a reactive approach (triggered by multiple remove clicks from the user)
After a remove call finishes, start the next remove call
Edit: What I would like to know is how to do/if is possible to do the solution I did (see edit2) using Rx operators.
Edit2: My solution for this was to enqueue the user operations (in this case remove) and emit, using a PublishSubject, when the UsersApi.refreshUser(userId) call finishes.
So basically what I did was (pseudo code):
private final PublishSubject<UserOperation> userOperationObs;
private final ConcurrentLinkedQueue<UserOperation> pendingOperations;
private boolean executingOperation;
private void emitUserOperation(final UserOperation operation) {
if (!executingOperation) {
executingOperation = true;
userOperationObs.onNext(operation);
} else {
executingOperation.add(operation);
}
}
public Observable<User> removeUser(UserOperation operation) {
return UsersApi.removeUser(operation.getUserId)
.switchMap(user -> UsersApi.refreshUser(operation.getUserId))
.doOnNext(user -> {
executingOperation = false;
final UserOperation nextOperation = pendingOperations.poll();
if (nextOperation != null) {
userOperationObs.onNext(operation);
}
};
}
You could turn your UI click into Observable (eg. by using RxBinding). After that, you could use concatMap operator to perform api call so it will start next network call once current api call is finished.
// emit clicks as stream
Observable<?> clicks = RxView.clicks(removeView)
// listen clicks then perform network call in sequence
clicks.concatMap(ignored -> usersApi.refreshUser(userId))
I am using RxJava for listening an event of Bluetooth adapter. The bluetooth listener may be active for long duration like for few hours. My question is how long the emitter is valid and I can send events though it?
My code is:
Class BlutoothObserver{
ObservableEmitter emitter;
BlutoothObserver(){
...
starListeningBluetoothDevice();
}
public Observer getObserver(){
return Observable.create(e -> {
emitter =e;
});
}
public void bluetoothCallback(){
...
emitter.onNext();
...
}
}
It should stay alive as long as you do not call onComplete() on emitter.
You will want to be careful with how you define getObserver() though. Right now you are creating a new Observable every time. So if you called getObserver() twice in a row it would return different Observable instances and only the last one returned would be of use. I would just create a field for the Observable and set it at construction time.
In an Android app, I'd like to refresh the list only once the user has stopped selecting a list of items in a List. So in effect, I'd like to the observer to be informed only once the producer has stopped emitting for at least 500ms.
Right now I have something like the following:
Subject<Object> _bus = PublishSubject.create().toSerialized();
...
_bus.onNext(new Event());
...
_bus.delay(500, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.observeOn(Schedulers.computation())
.subscribe(event -> {
// Do something with event
}));
This is fine, except it emits at 500 ms intervals even if the source is still emitting. I'd like to wait for 500ms to see if the source has stopped calling onNext() and only then emit.
Is this possible?
So basically you need debouncing with buffer. There is article which should helper you.
And kick off sample from that article:
Observable<Object> tapEventEmitter = _rxBus.toObserverable().share();
Observable<Object> debouncedEventEmitter = tapEventEmitter.debounce(1, TimeUnit.SECONDS);
Observable<List<Object>> debouncedBufferEmitter = tapEventEmitter.buffer(debouncedEventEmitter);
debouncedBufferEmitter.buffer(debouncedEventEmitter)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<Object>>() {
#Override
public void call(List<Object> taps) {
_showTapCount(taps.size());
}
});
I think you have to used debounce operator instead of delay eg.
_bus.debounce(500, TimeUnit.MILLISECONDS
.distinctUntilChanged()
.observeOn(Schedulers.computation())
.subscribe(event -> {
// Do something with event
}));
I'm trying to make a simple "button debouncer" which will count filtered clicks and display it thru a TextView. I want to filter rapid/spam clicks in a way that clicks with less than 300ms time-gap in-between are ignored.
I did my research and stumbled upon Rx's awesome debounce() which in theory should do the exact thing I wanted..
..or so I thought. As the app seemed to only register the first click; the counter won't increment no matter how long I tried to wait.
Here's a piece of my code:
...
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS)
.subscribe(new Subscriber<Object>() {
public int mCount;
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Object o) {
mText.setText(String.valueOf(++mCount));
}
});
...
What am I doing wrong? I've tried to run the thing without debounce() and it worked flawlessly (the counter will increment everytime the button got clicked).
Thanks in advance!
Note the following in the documentation on the debounce operator:
This variant operates by default on the computation Scheduler (...)
Or, code-wise, this currently happens:
public final Observable<T> debounce(long timeout, TimeUnit unit) {
return debounce(timeout, unit, Schedulers.computation());
}
As a result, the subscriber's callbacks are invoked on that same computation scheduler, since nothing is explicitly instructing otherwise.
Now, attempting to update a view (that's what's happening in onNext()) from any other thread than the main/ui thread, is a mistake and it will lead to undetermined results.
Fortunately, the remainder of the quote above provides the solution too:
(...) but you can optionally pass in a Scheduler of your choosing as a third parameter.
This would lead to:
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe(...);
Alternatively, you can still let the debounce happen on the computation scheduler, but receive the notifications on the main/ui thread:
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
Either way will ensure that the notifications are received on the main/ui thread and thus that the view is updated from the correct thread.