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
Related
I have a two queries which return two long values. I am setting these two long values to be displayed in individual text views. Finally I have a third text view which displays the combined value of both longs. I am having a problem getting the combined total to show as its setting the value before the livedata is returned.
Below is a snippet of the code
private void getData() {
mViewModelReframing.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> modelStatsTotalWorkouts) {
for (ModelStatsTotalWorkouts list : modelStatsTotalWorkouts) {
totalReframeWorkouts = list.getTotalWorkouts();
}
if (totalReframeWorkouts == 0) {
tvTotalReframes.setText(0 + getString(R.string.workouts_empty));
} else {
tvTotalReframes.setText("" + totalReframeWorkouts);
}
}
});
mViewModelCheckIn.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> tableCheckIns) {
for (ModelStatsTotalWorkouts list : tableCheckIns) {
totalCheckInWorkouts = list.getTotalWorkouts();
}
tvTotalCheckIns.setText("" + totalCheckInWorkouts);
// Combine both longs together for a combined total.
totalWorkouts = totalReframeWorkouts + totalCheckInWorkouts;
tvTotalWorkouts.setText("" + totalWorkouts);
}
});
}
Is there a better way to write the logic to achieve the desired result without the issue of the livedata not being returned fast enough?
Whenever you use independent Reactive streams like this (LiveData, RxJava, etc) you are going to have race conditions. You need to make explicit the dependencies for an action to happen - in this case your ability to update the UI in the way that you want had dependencies on BOTH APIs returning. This is the RxJava equivalent of zip. A few tips:
Consider using only a single Viewmodel for a view. The viewmodel should really be preparing data for your view specificially. In this case, it should really be that singular ViewModel that handles combining this data before passing it to your vew at all.
Barring that, since you've chosen LiveData here, you can do what you want by using a MediatorLiveData. Essentially, it acts as a composite stream source that depends on whichever other LiveData streams you add to it as described by that article. In this way, you can explicitly wait for all the needed values to arrive before you try to update the UI.
I solved the question by using this method:
public LiveData<List<ModelStatsTotalWorkouts>> totalWorkoutsCombined(long userId) {
LiveData liveData1 = database.getUsersDao().totalReframeWorkouts(userId);
LiveData liveData2 = database.getUsersDao().totalCheckInWorkouts(userId);
MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
return liveDataMerger;
}
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.
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.