With code as follows:
RxSeekBar.userChanges(seekbar)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.v(LOG_TAG, "last value: $it")
})
I am trying to catch user events of value change, problem is I don't see any logs in the console - no values emitted by observable. I am quite new to rxjava so if you have any advice I would be grateful!
Related
I was just writing some sample code with takeUntil -
final Observable<Integer> stopper = Observable.just(1)
.doOnComplete(() -> view.append("second stream complete"))
.delay(500, TimeUnit.MILLISECONDS);
return Observable
.range(0, 10)
.zipWith(Observable.interval(100, TimeUnit.MILLISECONDS), (item, interval) -> item)
.takeUntil(stopper)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(view::append);
So ideally, the stopper emits after 500ms and terminates the second observable, which it does. BUT the doOnComplete prints immediately.
As I understand from the documentation - delay shifts the events forward in time - including the complete event. So why would this happen?
If you look in the source code of the just (namely ScalarDisposable in ObservableScalarXMap.java) operator you will see that all it does is emits one onNext event with the provided value and immediately after that it emits onComplete event. In your example you've put doOnComplete before delay operator - that's why doOnComplete gets called immediately and after that the event is delayed for 500ms.
According to documentation, Observable.just() completes immediately - so message will be printed out right now.
If you want the message be printed after delay you should modify it like that
Observable.just(1)
.delay(500, TimeUnit.MILLISECONDS)
.doOnComplete(() -> view.append("second stream complete"))
In my application I have an array of notifications. Notification can be read and unread.
When user clicks on the unread notification I need to change the model and reload data in my table view.
In my ViewModel I have output stream:
let notifications: Driver<[Notification]>
And aslo I have an input stream with notification click:
let touchSingleNotificationIntent = PublishSubject<Notification>()
When I do something like this I get the error that it's let constant and I cannot mutate it.
touchSingleNotificationIntent
.filter { !$0.isRead }
.do(onNext: { notification in
notification.isRead = true // I need to change state of the model immediately after user iteration
})
.map { $0.notificationID }
.flatMap(markNotificationAsRead) // http request which doesn't reply with current notification model status
.subscribe()
.disposed(by: bag)
Do you have any ideas how to make it mutable? Thanks.
Streams aren't Mutable at all (this is the same for Observable, Driver, and any other traits). They are "Read only", you read values off the stream over time.
In general, the conception Observables has a "value" is a bit wrong since Observables represent a value over time, and not just a single value.
What you would want to do is "take into account" your PublishSubject when building out your driver.
Something like this would work:
notifications = Observable
.combineLatest(touchedNotification, readNotification, otherEvent) { ($0, $1, $2) }
.map { ... map the three values into whatever makes sense for you }
.asDriver(onErrorJustReturn: ... fallback value ... }
Again, the most important fact to remember - You do not actually mutate streams, you only combine them, transform them, etc, to create a new stream that suits your needs.
Hope this helps you!
Parameters of onNext are let by default. You can define a new one with var, i.e. 'var newNotification = notification' and then return it after modifying.
I have create one observable
Observable<Map<Integer, String>> observable = Observable.create(s -> {
try {
System.out.println("getMultipleCitiesName ==="+Thread.currentThread().getName());
List<String> cityIdsString = new ArrayList<>();
for (Integer cityId : cityIds) {
cityIdsString.add(cityId.toString());
}
MultiValueMap<String, String> formParams = new LinkedMultiValueMap<>();
formParams.put("cityIds[]", cityIdsString);
// Call the Location Client to call the API
Response<Map<Integer, String>> response = locationClient.getMultipleCitiesName(formParams);
s.onNext(response.getData());
} catch (Exception e) {
System.out.println("Inside Exception CITY NEW");
s.onError(e);
}
s.onComplete();
});
Now I want to add onErrorReturnItem() and retry() both.
So I tried it two ways
a) observable = observable.onErrorReturnItem(new HashMap<>()).retry(3);
b) observable = observable.retry(3).onErrorReturnItem(new HashMap<>());
b) is working (meaning retry and onErrorReturnItem) both are working
while in a) retry is not working?
Why is that
Order of operators matters, this is how you have to interpret the chains:
(a) you're saying something like Let take whatever comes from observable and if some error happens upstream then let have an observable that emit an empty HashMap(i.e calling onErrorReturnItem) and then retry on this observable(if some error is emitted). but no error is emitted by the observable onErrorReturnItem it just emit the empty HashMap followed by an onComplete signal. i.e whatever the error happens upstream is hidden by onErrorReturnItem and retry would never be signaled an onError to start retrying.
(b) you've saying completely the opposite, Let take whatever comes from observable and if some error happens upstream let retry, if retry can't get any valid item after 3 attempts, then signal onError downstream and onErrorReturnItem will return an empty HashMap.
Now it should be clear why (b) tend be what you're expecting to happens.
I've just started learning about #ngrx/store and #ngrx.effects and have created my first effect in my Angular/Ionic app. It runs ok the first time but if I dispatch the event to the store again (i.e when clicking the button again), nothing happens (no network call is made, nothing in console logs). Is there something obvious I'm doing wrong? Here's the effect:
#Effect() event_response$ = this.action$
.ofType(SEND_EVENT_RESPONSE_ACTION)
.map(toPayload)
.switchMap((payload) => this.myService.eventResponse(payload.eventId,payload.response))
.map(data => new SentEventResponseAction(data))
.catch((error) => Observable.of(new ErrorOccurredAction(error)));
Thanks
It sounds like an error is occurring. In that situation, the action in the observable returned by catch will be emitted into the effect's stream and the effect will then complete - which will prevent the effect from running after the error action is emitted.
Move the map and the catch into the switchMap:
#Effect() event_response$ = this.action$
.ofType(SEND_EVENT_RESPONSE_ACTION)
.map(toPayload)
.switchMap((payload) => this.myService
.eventResponse(payload.eventId, payload.response)
.map(data => new SentEventResponseAction(data))
.catch((error) => Observable.of(new ErrorOccurredAction(error)))
);
Composing the catch within the switchMap will prevent the effect from completing if an error occurs.
You must move map() and catchError() into swithchMap() as following
#Effect()
public event_response$ = this.action$.pipe(
ofType(SEND_EVENT_RESPONSE_ACTION),
switchMap((payload) => {
return this.myService.eventResponse(payload.eventId,payload.response).pipe(
map((data: DataType) => new SentEventResponseAction(data)),
catchError((error) => Observable.of(new ErrorOccurredAction(error)))
})
);
);
Please note that, evetResponse() method inside myService should return an observable in order to use pipe afterward.
In case your method inside service returns Promise, you can convert it into an observable by the use of from in the rxjs package as below:
import { from } from 'rxjs';
...
const promise = this.myService.eventResponse(payload.eventId,payload.response);
const observable = from(promise);
return observable.pipe(...
For more and detail description take a look at this link
i've an observable that I create with the following code.
Observable.create(new Observable.OnSubscribe<ReturnType>() {
#Override
public void call(Subscriber<? super ReturnType> subscriber) {
try {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(performRequest());
}
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
performRequest() will perform a long running task as you might expect.
Now, since i might be launching the same Observable twice or more in a very short amount of time, I decided to write such transformer:
protected Observable.Transformer<ReturnType, ReturnType> attachToRunningTaskIfAvailable() {
return origObservable -> {
synchronized (mapOfRunningTasks) {
// If not in maps
if ( ! mapOfRunningTasks.containsKey(getCacheKey()) ) {
Timber.d("Cache miss for %s", getCacheKey());
mapOfRunningTasks.put(
getCacheKey(),
origObservable
.doOnTerminate(() -> {
Timber.d("Removed from tasks %s", getCacheKey());
synchronized (mapOfRunningTasks) {
mapOfRunningTasks.remove(getCacheKey());
}
})
.cache()
);
} else {
Timber.d("Cache Hit for %s", getCacheKey());
}
return mapOfRunningTasks.get(getCacheKey());
}
};
}
Which basically puts the original .cache observable in a HashMap<String, Observable>.
This basically disallows multiple requests with the same getCacheKey() (Example login) to call performRequest() in parallel. Instead, if a second login request arrives while another is in progress, the second request observable gets "discarded" and the already-running will be used instead. => All the calls to onNext are going to be cached and sent to both subscribers actually hitting my backend only once.
Now, suppouse this code:
// Observable loginTask
public void doLogin(Observable<UserInfo> loginTask) {
loginTask.subscribe(
(userInfo) -> {},
(throwable) -> {
if (userWantsToRetry()) {
doLogin(loinTask);
}
}
);
}
Where loginTask was composed with the previous transformer. Well, when an error occurs (might be connectivity) and the userWantsToRetry() then i'll basically re-call the method with the same observable. Unfortunately that has been cached and I'll receive the same error without hitting performRequest() again since the sequence gets replayed.
Is there a way I could have both the "same requests grouping" behavior that the transformer provides me AND the retry button?
Your question has a lot going on and it's hard to put it into direct terms. I can make a couple recommendations though. Firstly your Observable.create can be simplified by using an Observable.defer(Func0<Observable<T>>). This will run the func every time a new subscriber is subscribed and catch and channel any exceptions to the subscriber's onError.
Observable.defer(() -> {
return Observable.just(performRequest());
});
Next, you can use observable.repeatWhen(Func1<Observable<Void>, Observable<?>>) to decide when you want to retry. Repeat operators will re-subscribe to the observable after an onComplete event. This particular overload will send an event to a subject when an onComplete event is received. The function you provide will receive this subject. Your function should call something like takeWhile(predicate) and onComplete when you do not want to retry again.
Observable.just(1,2,3).flatMap((Integer num) -> {
final AtomicInteger tryCount = new AtomicInteger(0);
return Observable.just(num)
.repeatWhen((Observable<? extends Void> notifications) ->
notifications.takeWhile((x) -> num == 2 && tryCount.incrementAndGet() != 3));
})
.subscribe(System.out::println);
Output:
1
2
2
2
3
The above example shows that retries are aloud when the event is not 2 and up to a max of 22 retries. If you switch to a repeatWhen then the flatMap would contain your decision as to use a cached observable or the realWork observable. Hope this helps!