I got 3 observable objects
Observable<CharSequence> o1 = RxTextView.textChanges(quitBuddyName);
Observable<CharSequence> o2 = RxTextView.textChanges(quitBuddyPhone);
Observable<CircleInviteListAdapter> o3 = RxAdapter.dataChanges(listAdapter);
Observable.zip(o1, o2, o3, (a, b, c) -> {
return a.length() > 0 && b.length() > 0 && c != null;
}).subscribe(finishBtn::setEnabled);
Question: Why finish button is not enabled after I set adapter first then enter text?
You're after combineLatest, not zip.
Zip needs results from all sources to produce a new one.
CombineLatest triggers after a change in either of the sources, but only after all 3 sources have produced at least one value.
Can't be sure from the code but here are 2 guesses.
1 - An error occurs. You're not doing anything onError so if an error occurs you're never going to know about it.
2 - Your Observable isn't emitting anything. Again, as you're not performing an Action onComplete you won't know if your Observable completes without emitting.
As noted in the documentation
It will only emit as many items as the number of items emitted by the
source Observable that emits the fewest items
So if one of your source Observables completes without emitting, then your zip doesn't do anything and your Subscribe just calls onComplete
In general, I would always recommend implementing at least onNext & onError, and usually I implement onComplete, even if it's to just log something at debug level.
Related
I have stared learning RxAndroid and have understood the reactive programming approach and power of different operators like flatmap, map, and xmap.
I have the following situation, I have a list of Reports say List<Reports> (obtained at runtime). I have to make 3 API calls for a report in a sequence (API 1 -> API 2 -> API 3) and repeat the same process for all reports in a sequence.
Example:
For Reports A, B, C, and D in List<Reports> make 3 API call for A and then for B and then for C and then for D. If anyone of 3 API calls fails for a report then stop the remaining call and continue for the next Report. Here the size of List<Reports> is known at runtime only.
How can I use RXjava to solve this problem?
Supposing you have the 4 API calls, a combineLatest operation would ideally return type Reports.
Observable<Report> result = Observable.combineLatest(
apiA,
apiB,
apiC,
apiD,
(respA, respB, respC, respD) -> respA + respB + respC + respD
)
.subscribeOn(
....
To be able to achieve the sequential order, you will need to wrap them in a flatMap operation:
Observable o = Observable.just(.....)// your initial data
o
.flatMap(value -> doCombineLatestOperation(value))
.subscribe(resp -> //Deal with data);
Hope that helps.
I'm making an Android application. I've to zip results from two places: One from SharedPreferences as a Maybe and other from the Room Persistence library(basically SQLite) as a Flowable.
I'm using the following code:
repository.getMaybe()
.subscribeOn(Schedulers.io)
.toSingle()
.zipWith(repository.getFlowable().single(DEFAULT VALUE), BiFunction { t1: DataType1, t2: DataType2 -> Pair(t1, t2) }
.subscribe()
repository.getMaybe() is the Maybe source mentioned in the first paragraph. Likewise, repository.getFlowable() is the Flowable source.
I've tried using doOnEvent(to Log statements) on the Maybe source, the Flowable source and the zipped source. Only the Maybe source emits successfully. Others don't do anything at all.
The Flowable source is used in various other parts of my application and it is not at all an issue.
What am I doing wrong?
If your Maybe does not return a value then calling toSingle would result in an Single.error. This would mean it would reach the zipWith as an arrow and it would never bother evaluating the value, since there is no value to zip with.
Thanks to Akarnokd, I tried removing the single(DEFAULT ITEM) part. It worked. On looking up the documentation at http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html, I came across this:
single(T defaultItem)
Returns a Single that emits the single item
emitted by the source Publisher, if that Publisher emits only a single
item, or a default item if the source Publisher emits no items.
Basically, the Flowable should emit only once. So, I'm using firstOrError() in it's place.
So i have a scenario in which i want to combine the newest results of two flowables and do something with it.
Flowable.combineLatest(
info,
list,
BiFunction { ... }
)
In certain conditions, i need to be able to get the the results again, and do some different stuff from before. So i could manually store the results of combinelatest somewhere, and then just reuse them but i was thinking, maybe there is a way to add a third flowable, and trigger onNext manually so the results are propagated again. Is this possible?
There are two approaches to keeping the computed value around for later use. You can create a BehaviorSubject that acts as an intermediate variable, that when defined will have the computed value, or you can publish() the observable so that newer subscribers will get the most recent results.
BehaviorSubject intermediateResult = BehaviorSubject.create();
Flowable.combineLatest(info, list, ...)
.subscribe( intermediateResult );
Alternatively,
Observable<Type> intermediateResult = Flowable.combineLatest(info, list, ...)
.replay(1)
.publish();
In either case, subscribing to intermediateResult will get the most recent computed value, if it is present.
Edit: make the function selectable on the fly:
Observable<FunctionSelector> fnSelector;
Observable<Type> intermediateResult =
Flowable.combineLatest(info, list, fnSelector,
(information, listToUse, selector) ->
getFunction(selector).apply(information, listToUse))
.replay(1)
.publish(1);
I have a subscription that wait for the push notification and another one that is polling the server to get response. I want to start both observable together and return the data from the one which finish first. What would be operator to use here?
Since you want to have the data of the first one to finish, you have to put the data somewhere until you get to the terminal event by collecting each into its own list and using amb that picks the source that signals an event (the collected list) first. Then you can unroll the list back to individual items.
Observable<A> source1 = ...
Observable<A> source2 = ...
Observable.amb(source1.toList(), source2.toList())
.flatMapIterable(list -> list)
.subscribe(...);
The operator you are looking for is first. Of-course, you'll have to merge the Observables first (by using merge, or probably better - mergeDelayError, so if only one of them fails, you'll still get the first which finishes with a vaild result).
Should look like:
Observable.mergeDelayError(pushObservable, pullObservable)
.first()
.subscribe(data->...);
I have list coming back from a REST endpoint. I need to break that list down into categories (category is an item in each entry of the list). Individual categories will be written to a cache for faster lookup later.
I didn't know if I could .map() the entries and supply multiple filter() or some type of case statement to put the category entries in the right bucket.
Does something like this sound reasonable to implement with rxJava?
UPDATE:
Non-working version
private Map<String, List<VideoMetadataInfoEntity>> buildCategories( Observable<List<VideoMetadataInfoEntity>> videoList ) {
Map<String, List<VideoMetadataInfoEntity>> categoryMap = new HashMap<>();
videoList
.flatMap( Observable::from )
.subscribe( videoMetadataInfoEntity -> mapCategory(videoMetadataInfoEntity, categoryMap ) );
Observable.just( categoryMap )
.doOnNext( saveCategoriesToCacheAction );
return categoryMap;
}
These fire in sequence, however, and this is my understanding, the second observable is not sending anything the saveCategoriesToCacheAction since it hasn't subscribed to the result of the first observable.
I am starting to think I should modify my cache strategy. The list will always have all the details. The service doesn't provide me a subset that I can use for listing and then another call to get the full details. It is either full list or full details for one item. It might be a better approach to just cache each one individually and into their own category caches right now. I was trying to do the map so that this network call could return the requested category, but subsequent calls would come from the cache, until such time as the cache has expired and a new network call refreshes it.
My solution is:
Observable.range(1, 20)
.groupBy(number -> number % 2)
.flatMap(groupedObservable -> groupedObservable.toList())
.toMap(list -> list.get(0) % 2);
As a result I have [{0=[2, 4, 6, 8, 10, 12, 14, 16, 18, 20], 1=[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]}]
Explanation:
range(1, 20) - creates an observable which emits first twenty numbers
groupBy(number -> number % 2) - creates an observable that emits group observables where each group observable holds items grouped with the grouping function(here it is x % 2)
flatMap(groupedObservable -> groupedObservable.toList()) - turns each group into an observable that emits all its items as a list
toMap(list -> list.get(0) % 2) - creates the map
RxJava is more for asynchronous message processing, but as it also espouses functional programming principles it could be used as a poor man's stream api. If you are using Java 8 consider using streams to do this job, but as you are asking this question I assume you are using Java 7.
To do what you want you could try (forgive the lambda, substitute it with an anonymous inner class if you are not using Retrolambda):
Observable.from(list).subscribe(item -> groupItemInCategoryBucket(item));
where groupItemInCategoryBucket is your method that contains the switch statement or whatever other way you have of caching the items.
Please note that this is the equivalent of a for loop, and although it is idiomatic to use this style in many other nice languages, a lot of Java developers might be a bit puzzled when they see this code.
Generally grouping of items can be achieved using a groupBy operator (for more information about it visit this page).
Map<Integer, List<Integer>> groupedValues = new HashMap<>(4);
Observable.range(1, 20)
.groupBy(i -> i % 2, i -> i)
.subscribe(go -> {
List<Integer> groupValues = new ArrayList<>();
groupedValues.put(go.getKey(), groupValues);
go.subscribe(t -> add(t, groupValues));
});
How it works:
Firstly, observable emits items 1 through 20 (this happens in range method)
Which then are emitted to separate observables based on their
parity(groupBy method, after this method you operate on GroupedObservable)
You then subscribe to the grouped observable, receiving (in subscribers onNext) separate observables that will contain grouped items and the key they were grouped by.
Remember to either subscribe to the grouped observables or issue take(0) on them if their content does not interest you to prevent memory leaks.
I am not sure whether it is the most efficient way or not and would welcome some input about this solution.