I have a list of transactions. Each transaction has currency and amount information among others. I want to create a list of holdings, so the current amount held by currency. I started with groupBy() and continued with reduce. It seems I have to subscribe before I can do anything with the results, because this gives me an error:
Observable.fromIterable(transactions)
.groupBy(Transaction::getCurrency)
.flatMap(t -> t.reduce(new Holding(t.getKey()), (holding, transaction) -> holding.addTransaction(transaction.getAmount()))
It says "no instance of type variable R exist so that Single conforms to ObservableSource< ? extends R>".
On the other hand if I try this:
Observable.fromIterable(transactions)
.groupBy(Transaction::getCurrency)
.subscribe((GroupedObservable<String, Transaction> r) -> r.reduce(new Holding(r.getKey()), (holding, transaction) -> holding.addTransaction(transaction.getAmount()))
.toObservable()
.subscribe(t -> {
//t is a single Holding.
}
));
I cannot get a list, because I already subscribed to the grouped stream. I could add it up, but I'm pretty sure there is a more elegant solution, but I cannot figure it out.
Solution based on akarnokd's answer:
Observable.fromIterable(transactions)
.groupBy(Transaction::getCurrency)
.flatMapSingle(Observable::toList)
.map(Holding::new)
.toList()
.subscribe(holdings -> {
whatever(holdings);
});
(From my comment to the post):
Try flatMapSingle in the upper case. Also, subscribing from within an onNext handler is a bad practice as you lose the composition properties of RxJava.
This will work for sure
public Single<Map<Integer, List<Category>>> getSubCategoryListById(List<Category> categoryList) {
return Flowable.just(categoryList)
.flatMapIterable(new Function<List<Category>, Iterable<Category>>() {
#Override public Iterable<Category> apply(List<Category> categories) throws Exception {
return categories;
}
})
.filter(new Predicate<Category>() {
#Override public boolean test(Category category) throws Exception {
return category.parent_id != 0;
}
})
.groupBy(new Function<Category, Integer>() {
#Override public Integer apply(Category category) throws Exception {
return category.category_id;
}
})
.flatMapSingle(new Function<GroupedFlowable<Integer, Category>, Single<List<Category>>>() {
#Override public Single<List<Category>> apply(
GroupedFlowable<Integer, Category> integerCategoryGroupedFlowable) throws Exception {
return integerCategoryGroupedFlowable.toList();
}
})
.toMap(new Function<List<Category>, Integer>() {
#Override public Integer apply(List<Category> categories) throws Exception {
return categories.get(0).category_id;
}
});
}
As the documentation says, the reduce function
applies a function to each item emitted by an Observable,
sequentially, and emit the final value.
This is way you get a single value (actually for each Observable of the group you get a single item).
You can defer your reduce operation after you get a list. You could probably replace your first long subscribe with this:
.subscribe(group -> group.toList()
Then you get some Observables based on the number of groups that you have, each emitting a single List of your predefined type.
NOTE: not sure about it, but probably you can replace the first subscribe with a flatMap that transforms your GroupedObservable into an Observable that emit a list of items.
Related
I have a ViewPager with two pages namely Popular and All. What I'm trying to achieve is only push items that have popular tag true to Popular whereas push all items to All.
Currently I have a single class which is used in the PagerAdapter and passing in the page type. How do I filter out PublishSubject so that each page only displays necessary items accordingly.
Both my Observer are subscribed to a single PublishSubject, but I
want to filter when emitting.
Please comment if the question is unclear. I'll try my best to relay this problem. Also sorry if it has already been answered since I couldn't find anything relevant.
The code I'm using is this based on this architecture in which I have a Firebase data store FirebaseSubscriptionDataStore which provides the PublishSubject. This is later subscribed to by SubscribeToSubscriptionUpdates in SubscriptionListPresenterImpl
Thanks in advance.
You can basically define two different methods to get Observable (or Flowable) from PublishSubject. First observable will emit all of the items and second one only popular ones:
public class DataStore {
private PublishSubject<DataItem> dataItemPublishSubject = PublishSubject.create();
public Flowable<DataItem> getAllObservable() {
return dataItemPublishSubject.toFlowable(BackpressureStrategy.BUFFER);
}
public Flowable<DataItem> getPopularObservable() {
return dataItemPublishSubject.toFlowable(BackpressureStrategy.BUFFER)
.filter(new Predicate<DataItem>() {
#Override
public boolean test(DataItem dataItem) throws Exception {
return dataItem.popular;
}
});
}
public static class DataItem {
public final boolean popular;
public DataItem(boolean popular) {
this.popular = popular;
}
}
}
In case you don't want to two methods, you can move .filter() operator everywhere within you Rx chain and you might end up with something like this:
dataStore.getAllObservable()
.doOnNext(new Consumer<DataStore.DataItem>() {
#Override
public void accept(DataStore.DataItem dataItem) throws Exception {
pagerAdapter.addDataAll(dataItem);
}
})
.filter(new Predicate<DataStore.DataItem>() {
#Override
public boolean test(DataStore.DataItem dataItem) throws Exception {
return dataItem.popular;
}
})
.doOnNext(new Consumer<DataStore.DataItem>() {
#Override
public void accept(DataStore.DataItem dataItem) throws Exception {
pagerAdapter.addDataPopular(dataItem);
}
})
.subscribe();
Is it possible to convert an observable like Single to Single? I believe that the operator compose() is used for this purpose but I am lost in how to implement it
What I want to achieve here could be more clearly seen in the following code snippet.
#Override
public Single<SystemDefaults> getSystemDefaults() {
SystemDefaults systemDefaults = new SystemDefaults();
return systemDao.getRoles().compose((SingleTransformer<List<Role>, SystemDefaults>) upstream -> {
//WHAT SHOULD I DO HERE?
});
}
I am pretty new to RxJava(Android) so am pretty lost here.
You need the map operator. Map takes every item your Observables (or Singles Maybes, etc) and transform it to another value:
Observable.just(1) // Start emitting Integers
.map(number -> number.toString()}) // Transform it to String
.subscribe(someString -> System.out.println(someString.getClass())); // Receive an String
Yes the operator you´re looking it´s compose and it´s used to transform an observable/single from your previous observable/single.
Here an example how I transform an Integer observable to String observable.
Observable.Transformer<Integer, String> transformIntegerToString() {
return observable -> observable.map(String::valueOf);
}
/**
* In this example we use a transformer to get the Integer item emitted and transform to String
*/
#Test
public void observableWithTransformToString() {
Observable.just(1)
.map(number -> {
System.out.println("Item is Integer:" + Integer.class.isInstance(number));
return number;
})
.compose(transformIntegerToString())
.subscribe(number -> System.out.println("Item is String:" + (String.class.isInstance(number))));
}
You can see another example how to change it to another observable in another thread here. https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/transforming/ObservableCompose.java
Scenario: RXJava 2. Android.
Imagine you have an Observable from iterable like so: Observable.fromIterable(arrayList<Something>) and you need to do two things with it:
Filter the items.
Know if an item was filtered out (a.k.a.: the filter function returned false at least once).
This is a similar observable (extremely simplified to be relevant):
final ArrayList<Something> someArrayList = getTheArrayList();
Observable
.fromIterable(someArrayList)
.subscribeOn(Schedulers.computation())
.filter(new AppendOnlyLinkedArrayList.NonThrowingPredicate<Something>() {
#Override
public boolean test(final Something something) {
return something.isValid();
}
})
.toList()
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess(new Consumer<List<Something>>() {
#Override
public void accept(#NonNull final List<Something> somethings)
throws Exception {
// How can I tell if the filter above returned false
// at least once?
}
})
.subscribe();
To answer the above question, one option is to compare the original someArrayList to somethings. If they are different, well, something happened. But this means the list have to have the same items in the same order, which can be a problem if Something is a complicated object, that must implement an equals.
The plan B, which I am using and I don't like is to keep a "boolean array" outside the observable, like so:
final boolean[] hasInvalidData = new boolean[1];
and then in the .filter I can do:
final isValid = something.isValid();
if (!isValid) {
hasInvalidData[0] = true;
}
return isValid;
and in success, I could simply do:
if (hasInvalidData[0]) {
// Something has been filtered
}
The question is: is there a better approach?
UPDATE:
What I have done so far is simply compare originalList.size() with finalEmitedList.size(). If they are different, that means my filter "filtered" something.
The way that I read it, this smells like an XY problem. If you don't need to keep track of the elements and you just need to compute until you find one element, everything becomes much easier:
Observable
.fromIterable(someArrayList)
.subscribeOn(Schedulers.computation())
.map(something -> something.isValid())
.filter(bool -> bool)
.first(false);
If you actually need the list of elements:
Observable<Something> source = Observable
.fromIterable(someArrayList)
.subscribeOn(Schedulers.computation())
.publish()
.autoConnect(2);
source
.map(something -> something.isValid())
.reduce(false, (a,b) -> a | b)
.zipWith(source.toList(), (flag, list) -> {
// do your stuff
})
.subscribe();
not sure it works but Observable.sequenceEqual could work:
ArrayList<Object> list = ...
Predicate myFilter = ...
Observable<Object> observable = Observable.fromIterable(list);
Observable.sequenceEqual(observable, observable.filter(myFilter))
.subscribe(new Consumer<Boolean>() {
#Override
public void accept(#NonNull Boolean changed) throws Exception {
// result here
}
});
I have to make a call to an API that returns a list of items. For each item of this list, I have to make a call to another API (if the list returns 8 items, I will have to make 8 parallel calls).
I finally have to return a list that I will create with the results of each of these 8 parallel calls.
How can I do that with RxJava ? I think that I have to use a flatMap to transform the result of the first call to a list of Observables, and then I have to use the zip operator to make the parallel calls, but I'm not sure.
Please note that I'm using RxJava2, and without lambdas expressions.
Thanks !
you can do it like this for example,
defer() lets you fetch the data only when subscribing and then creating Observable that emits all items (one by one) in the list of items.
then flatMap() will create Observable that will fetch the data for each item, and you will have now Observable that emit Data objects.
in order to collect it, you can use toList() that will emit single object ( a List) that will contain all the Data fetched by each Observable.
Note, in order to do it in parallel it is important that the fetchDataFromItem() will subscribe on Schedulers.io(), even it the all stream is subscribed on io.
Observable.defer(new Callable<ObservableSource<Item>>() {
#Override
public ObservableSource<Item> call() throws Exception {
List<Item> items = getItems();
return Observable.fromIterable(items);
}
})
.flatMap(new Function<Item, ObservableSource<Data>>() {
#Override
public ObservableSource<Data> apply(#NonNull Item item) throws Exception {
return fetchDataFromItem(item);
}
})
.toList()
.subscribe(new Consumer<List<Data>>() {
#Override
public void accept(#NonNull List<Data> objects) throws Exception {
//do something with the list of all fetched data
}
});
UPDATE:
in case that the items fetching is already Observable, the defer() can be replaced with flatMapIterable() that takes single List of items and transform it to Observable of multiple items:
getItemsObservable()
.flatMapIterable(new Function<List<Item>, Iterable<Item>>() {
#Override
public Iterable<Item> apply(#NonNull List<Item> items) throws Exception {
return items;
}
})
.flatMap(new Function<Item, ObservableSource<Data>>() {
#Override
public ObservableSource<Data> apply(#NonNull Item item) throws Exception {
return fetchDataFromItem(item);
}
})
.toList()
.subscribe(new Consumer<List<Data>>() {
#Override
public void accept(#NonNull List<Data> objects) throws Exception {
//do something with the list of all fetched data
}
});
I have the following observable to get list of feed ids from database ( i use sugar ORM library)
public Observable<Set<Long>> getFeedIdsFromDB() {
return Observable.create(subscriber -> {
Set<Integer> subscribedFeedIds = new HashSet<>();
//get feed ids from FeedEntity Table
for (FeedEntity feed : FeedEntity.listAll(FeedEntity.class)){
if (feed.isSubscribed()){
subscribedFeedIds.add(feed.getFeedId());
}
}
});
}
this Observable should emits ids to be used for api call in the following:
public Observable<StoryCollectionEntity> storyEntityList(final int page) {
return this.restApi.storyCollection(/* this is feed ids*/ id, page)
.distinct(storyCollectionEntity -> storyCollectionEntity)
.doOnNext(saveStoryCollectionToCacheAction)
}
i guess i should use some sort of mapping but have no idea how can i implement it.
EDIT:
i did the following modification:
// To map feed ids (retrieved from database) to getAllStoryEntityList Observable:
#Override
public Observable<StoryCollectionEntity> storyEntityList(final int page) {
return this.mNewsCache.getFeedIdsFromDB().flatMap(id -> getAllStoryEntityList(page, id));
}
//call restApi
public Observable<StoryCollectionEntity> getAllStoryEntityList(final int page, Set<Long> id){
return this.restApi.storyCollection( id, page)
.distinct(storyCollectionEntity -> storyCollectionEntity)
.doOnNext(saveStoryCollectionToCacheAction);
}
but api service is never called. something wrong in the mapping.
#GET("story")
Observable<StoryCollectionEntity> storyCollection(
#Query("feed_ids") Set<Long> feedIds,
#Query("page") int page);
The Observable created in getFeedIdsFromDB isn't emitting any items, so your flatMap and other data transformations never occur because the stream actually has no data. You can test this by subscribing directly to the returned Observable and doing something for onNext.
getFeedIdsFromDB().subscribe(feedId -> System.out.println(feedId));
You should see that nothing gets printed. When using Observable#create, the onNext method of subscriber in the anonymous class must be manually called with whatever data you wish to pass downstream. The documentation provides sample code for this.
So modifying your Observable to call onNext, we get this:
public Observable<Set<Long>> getFeedIdsFromDB() {
return Observable.create(subscriber -> {
Set<Integer> feedIds = new HashSet<>();
// get feed ids from FeedEntity Table
for (FeedEntity feed : FeedEntity.listAll(FeedEntity.class)){
feedIds.add(feed.getFeedId());
}
// emit a single Set and complete
if (subscriber.isSubscribed()) {
subscriber.onNext(feedIds);
subscriber.onCompleted();
}
});
}
Now the Set should get passed along. If your goal is to end up with a the emission of a single StoryCollectionEntity object after the transformations (and if I'm reading this properly), then your mapping looks correct.
I'm not sure what the expected output is, but I'll show you a way to do this sort of thing. Maybe you can alter it to fit your use case.
The first step is to allow id as a function parameter in storyEntityList:
public Observable<StoryCollectionEntity> storyEntityList(final int page, int id) {
return this.restApi.storyCollection(/* this is feed ids*/ id, page)
.distinct(storyCollectionEntity -> storyCollectionEntity)
.doOnNext(saveStoryCollectionToCacheAction)
Now you can use an Observable.flatMap:
public Observable<StoryCollectionEntity> getAllStoryEntityList(int page){
return getFeedIdsFromDB().flatMap(id -> storyEntityList(page, id));
}
The naming might be off, but again - I'm not sure what the entities are.