Is there a way to distinct results when doing Observable.merge(o1, o2)?
I'm actually using this snippet of code:
public Observable<List<GalleryItem>> getData(int page) {
return network(page).publish(network -> Observable.merge(network, cache().takeUntil(network))).onErrorResumeNext(cache());
}
The network() method is used as a remote data source which saves a list of items to a Room DAO and returns it afterwards.
The cache() method is simply retrieving the list from the database.
private Observable<List<GalleryItem>> network(int page) {
return service
.getItems(page)
.flatMap(new MapGalleryResponseToPosts(true))
.doOnNext(galleryItems -> {
mTopDao.nukeTable();
mTopDao.saveImages(galleryItems);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io());
}
private Observable<List<GalleryItem>> cache() {
return Observable.fromCallable(mTopDao::loadImages).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
It does what it's supposed to do, the cache is displayed before the network call, when the network call executes it's basically duplicating (some) items.
I've tried to use distinct() but apparently it doesn't work.
I have also generated the hashCode and equals() because I read that distinct() depends on it.
Generate equals and hashcode, for your GalleryItem object. Distinct() operator will work as expected. Try following code..
public Observable<List<GalleryItem>> getData(int page) {
return Observable.merge(network(page), cache()).distinct()).onErrorResumeNext(cache());
}
Simple use case to check distinct() operator,
Observable<Integer> values1 = Observable.create(o -> {
o.onNext(1);
o.onNext(1);
o.onNext(2);
o.onNext(3);
o.onNext(2);
o.onComplete();
});
Observable<Integer> values2 = Observable.create(o -> {
o.onNext(1);
o.onNext(1);
o.onNext(1);
o.onNext(2);
o.onNext(3);
o.onNext(8);
o.onNext(9);
o.onNext(10);
o.onComplete();
});
Observable.merge(values1, values2).distinct().subscribe(
v -> System.out.println(v),
e -> System.out.println("Error: " + e),
() -> System.out.println("Completed")
);
Check the result with distinct() and without distinct() operator.
Hope this helps you.
Related
Update: This was how my old insertIntoDb method looked like which didn't work :
private Completable insertIntoDb(List<ArticleEntity> articleItemEntities) {
return database.articleDao().insertArticles(articleItemEntities)
.observeOn(Schedulers.io());
}
I changed it to the following and now it works :
private void insertIntoDbNew(List<ArticleEntity> articleItemEntities) {
mCompositeDisposable.add(
database.articleDao().insertArticles(articleItemEntities)
.subscribeOn(Schedulers.io())
.subscribe());
}
I don't know why but now it works. Sure the worker completes before the database insert completes but that doesn't seem to be a problem which I believed before.
End of update.
I'm new to reactive programming. My goal is to schedule a work manager to do 4 Actions then return a result using RxJava2. Here are the tasks I want to perform
Do a web api call.
Structure the data we get from the API call.
Insert it into our local room database.
When everything is complete signal Result.success() back to the Job so it knows that everything went ok and can terminate.
So my preferred method would look something like this.
public Result doWork(){
return api.get("URL") responseData -> structureData(responseData) structuredData -> insertIntoDB(structuredData) -> Result.success()
}
I'm using RxJava2 and the RxWorker class.
Below is my current solution. Is this correct or am I doing something wrong?
public class DownloadWorker extends RxWorker {
#Override
public Single<Result> createWork() {
return apiService.download("URL")
.map(response -> processResponse(response))
.doOnSuccess(data -> insertIntoDb(data))
.flatMap(response ->
allComplete()
)
.observeOn(Schedulers.io());
}
Single<Result> allComplete() {
return Single.just(Result.success());
}
}
It behaves like I want it to. It downloads the data, structures it, then inserts it into the DB then returns Result.success(). But I have no idea what I am doing. Am I using RxJava as it was intended?
Also this part bothers me :
.flatMap(response -> allComplete())
the response part is superfluous can I remove it somehow?
I did some improvements to your code:
public class DownloadWorker extends RxWorker {
#Override
public Single<Result> createWork() {
return apiService.download("URL")
.map(response -> processResponse(response))
.flatMapCompletable(articleItemEntities -> database.articleDao().insertArticles(articleItemEntities))
.toSingleDefault(Result.success())
.onErrorReturnItem(Result.failure())
.observeOn(Schedulers.io());
}
}
In original code, you save data using doOnSuccess method which is a side effect. As you mentioned in your comment, insertIntoDb() method returns Completable. Therefore, I changed doOnSuccess(data -> insertIntoDb(data)) to flatMapCompletable(data -> insertIntoDb(data)) which will allow you make sure storing data succeeded and wait until it finishes. As insertIntoDb() method returns Completable and createWork() method has to return Result, we have to now change type from Completable to Single<Result>. Therefore, I used toSingleDefault which returns Result.success() by default. Also, I added onErrorReturnItem(Result.failure()) which will allow RxWorker to track errors. I hope my answer helps.
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
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.
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 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.