My observable.fromIterable is just returning 1 time.
I am trying to return a list of elements like Observable objects. For that I am using "fromIterable". topMoviesRated.getResults() is returning 20 objects, doOnNext is being callin 20 times but the method getResultFromNetwork is just returning the first element.
Presenter:
public void loadData(boolean error) {
subscription = model.result(error)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<MyFilm>() {
#Override
public void onNext(MyFilm object) {
if(view!=null){
view.updateData(object);
}
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
if(view!=null){
view.showSnackbar("Error...");
}
}
#Override
public void onComplete() {
if(view!=null){
view.showSnackbar("Finished...");
}
}
});
}
Model:
public Observable<MyFilm> result(boolean error) {
if (error)
repository.setPagination(1);
else
repository.setPagination(++pagination);
return Observable.zip(repository.getResultFromNetwork(), detailResult(), new BiFunction<Result, Details, MyFilm>() {
#Override
public MyFilm apply(Result result, Details details) {
return new MyFilm(result.getOriginalName(), result.getId().toString(), details);
}
});
}
public Observable<Details> detailResult() {
return Observable.zip(repository.getDetailsData(), repository.getCountryData(), new BiFunction<TVDetails, String, Details>() {
#Override
public Details apply(TVDetails tvDetails, String country) {
return new Details(tvDetails, country);
}
});
}
Repository:
public Observable<Result> getResultFromNetwork() {
Observable<TopMoviesRated> topMoviesRatedObservable = moviesApiService.getTopMovies("es_ES", pagination);
return topMoviesRatedObservable.concatMap(new Function<TopMoviesRated, Observable<Result>>() {
#Override
public Observable<Result> apply(TopMoviesRated topMoviesRated) {
return Observable.fromIterable(topMoviesRated.getResults());
}
}).doOnNext(new Consumer<Result>() {
#Override
public void accept(Result result) {
Log.d("OnNext -->", result.toString());
}
});
}
Check out this image. The first list has 5 items and second only 4. As you see, result of zip operator contains only 4 items.
Yamko is right, zip is not what you need. Try to use Observable.combineLatest instead of it
Related
I want to combine a Flowable with an Observable to create a Model from that sources.
From Room I get CartModel items this models contains an id of product and quantity.
#Override
public Flowable<List<CartModel>> getCartItems() {
return appLocalDataStore.getCartItems();
}
After that I have a function that for every id from above function compose a Pair of quantity and Product Details.
#Override
public Observable<List<Pair<WsProductDetails, Integer>>> getCartWithProductData() {
return appLocalDataStore.getCartItems().toObservable().flatMap(new Function<List<CartModel>, ObservableSource<CartModel>>() {
#Override
public ObservableSource<CartModel> apply(List<CartModel> cartModels) throws Exception {
return Observable.fromIterable(cartModels);
}
}).flatMap(new Function<CartModel, ObservableSource<Pair<WsProductDetails, Integer>>>() {
#Override
public ObservableSource<Pair<WsProductDetails, Integer>> apply(final CartModel cartModel) throws Exception {
return appRemoteDataStore.getProductBySKU(cartModel.getId()).map(new Function<WsProductDetails, Pair<WsProductDetails, Integer>>() {
#Override
public Pair<WsProductDetails, Integer> apply(WsProductDetails wsProductDetails) throws Exception {
Log.d("TestCartItems", "SKU" + wsProductDetails.getSku() + " quantity" + cartModel.getQuantity());
return new Pair<>(wsProductDetails, cartModel.getQuantity());
}
});
}
}).toList().toObservable();
}
#GET(PATH_PRODUCT_DETAILS_BY_SKY)
Observable<WsProductDetails> getProductBySKU(#Path(PATH_CODE) String cod);
Above function works ok , because the logs are corect.
But in My Presenter OnNext()||OnError()||onComplete() function is never called.
Why this happens?
#Override
public void getCartItems() {
Observable<List<Pair<WsProductDetails, Integer>>> source = appDataStore.getCartWithProductData();
mCompositeDisposable.add(source
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribeWith(new DisposableObserver<List<Pair<WsProductDetails, Integer>>>() {
#Override
public void onComplete() {
Log.d(TAG, "onComplete()");
}
#Override
public void onNext(List<Pair<WsProductDetails, Integer>> pairs) {
Log.d(TAG, "onError()");
view.showCartItems(pairs);
}
#Override
public void onError(Throwable e) {
Log.d(TAG, "onError()");
}
}));
}
}
You have to apply the lookup flatMap to the inner fromIterable so it gets finite:
#Override
public Observable<List<Pair<WsProductDetails, Integer>>> getCartWithProductData() {
return appLocalDataStore.getCartItems().toObservable().flatMap(new Function<List<CartModel>, ObservableSource<CartModel>>() {
#Override
public ObservableSource<CartModel> apply(List<CartModel> cartModels) throws Exception {
return Observable.fromIterable(cartModels)
.flatMap(new Function<CartModel, ObservableSource<Pair<WsProductDetails, Integer>>>() {
#Override
public ObservableSource<Pair<WsProductDetails, Integer>> apply(final CartModel cartModel) throws Exception {
return appRemoteDataStore.getProductBySKU(cartModel.getId())
.map(new Function<WsProductDetails, Pair<WsProductDetails, Integer>>() {
#Override
public Pair<WsProductDetails, Integer> apply(WsProductDetails wsProductDetails) throws Exception {
Log.d("TestCartItems", "SKU" + wsProductDetails.getSku() + " quantity" + cartModel.getQuantity());
return new Pair<>(wsProductDetails, cartModel.getQuantity());
}
});
}
}).toList().toObservable();
}
});
}
public Observable<Void> onFabClicked() {
return RxView.clicks(a_button);
}
{
view.onOutBoundBtClicked()
.observeOn(computationScheduler)
.map(new Func1<Void, ViewModelAction>() {
#Override public ViewModelAction call(Void aVoid) {
return new OutboundInputFieldTappedAction();
}
}).mergeWith(view.onFabClicked().flatMap(new Func1<Void, Observable<ListOfResult>>() {
#Override
public Observable<ListOfResult> call(Void avoid) {
return listRepository.getListResult("UK",
"en-GB",
).toObservable();
}
}).map(new Func1<ListOfResult, ViewViewModelAction>() {
#Override
public ViewViewModelAction call(ListOfResult ListOfResult) {
return new ListOfResultsReceivedAction(ListOfResult);
}
}))
.mergeWith(view.onFabClicked()
.observeOn(computationScheduler)
.map(new Func1<Void, ViewModelAction>() {
#Override
public ViewModelAction call(Void aVoid) {
return new SearchPanelAnimationAction();
}
}))
I have an observable1 which is come from RxView.clicks(a_button), how can I create another observable2 from this click observable1, then emit observable1 and observable2 to a same subscriber? I tried flatmap that convert observable1 to ovservable2 but then observalbe1 is never heard, the code is
{
view.onOutBoundBtClicked().observeOn(computationScheduler).map(new Func1() {
#Override
public ViewModelAction call(Void aVoid) {
return new OutboundInputFieldTappedAction();
}
}).mergeWith.
, and why I always return an XXXAction is that I will change some fields in the viewModel, and I will use
.withLatestFrom(viewModels, new Func2() {
#Override
public ActivityViewModel call(ViewModelAction action, ActivityViewModel activityViewModel) {
return action.modifyViewModel(activityViewModel);
}
so when ever something changed in the viewmodel I will get an event emitted and do some thing upon it, thanks, guys.
You need to create an observable field that is shared, and expose that instead of returning RxView.clicks() directly.
Observable<Void> fabClicks;
public void onCreate(Bundle bundle) {
...
fabClicks = RxView.clicks(a_button).share();
}
public Observable<Void> onFabClicked() {
return fabClicks;
}
Following this tutorial I've created two sources for fetching data. I expect that if there is no data locally I'll send network request. But all the time get list of null objects from local source (which is first in Observable.concat).
For local source using SQLite with SQLBrite wrapper and Retrofit for remote source:
#Override
public Observable<Item> get(String id) {
//creating sql query
return databaseHelper.createQuery(ItemEntry.TABLE_NAME, sqlQuery, id)
.mapToOneOrDefault(mapperFunction, null);
}
There is method in repository for concating observables:
#Override
public Observable<Item> get(String id) {
Observable<Item> local = localDataSource
.get(id)
.doOnNext(new Action1<Item>() {
#Override
public void call(final Item item) {
// put item to cache
}
});
Observable<Item> remote = remoteDataSource
.get(id)
.doOnNext(new Action1<Item>() {
#Override
public void call(final Item item) {
// save item to database and put to cache
}
});
return Observable.concat(local, remote).first();
}
For getting it with list of ids I'm using next method:
#Override
public Observable<List<Item>> getList(final List<String> ids) {
return Observable
.from(ids)
.flatMap(new Func1<String, Observable<Item>>() {
#Override
public Observable<Item> call(String id) {
return get(id);
}
}).toList();
}
And subscription in fragment:
Subscription subscription = repository
.getList(ids)
.flatMap(new Func1<List<Item>, Observable<Item>>() {
#Override
public Observable<Item> call(List<Item> result) {
return Observable.from(result);
}
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Item>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Item> result) {
// there get list of null objects
}
});
So, main goal is first check local storage and if there is no item - make request to server. But now if item isn't exist I get null instead of send request.
Could someone help me understand why?
Calling first() after concat will of course return the first item, regardless if it's valid.
Yout first() function should validate the value and only submit a 'valid' first item.
Something like:
public void test() {
Integer[] ids = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Observable.from(ids)
.flatMap(new Func1<Integer, Observable<String>>() {
#Override
public Observable<String> call(Integer id) {
return get(id);
}
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<String>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<String> strings) {
}
});
}
public Observable<String> get(int id) {
return Observable.concat(getLocal(id), getRemote(id))
.first(new Func1<String, Boolean>() {
#Override
public Boolean call(String s) {
return s != null;
}
});
}
public Observable<String> getLocal(int id) {
return Observable.just(id < 5 ? "fromLocal id:"+id : null);
}
public Observable<String> getRemote(int id) {
return Observable.just(id >= 5 ? "fromRemote id:"+id : null);
}
Will bring u the result:
fromLocal id:1
fromLocal id:2
fromLocal id:3
fromLocal id:4
fromRemote id:5
fromRemote id:6
fromRemote id:7
fromRemote id:8
fromRemote id:9
fromRemote id:10
Answer from github:
Your function Observable get() returns endless Observable because
mapToOneOrDefault does not complete Observable on its own and reacts to
updates of the db. You need to limit emission of this Observable because
operator concat waits for onCompleted event.
For example, it should looks like:
return Observable.concat(localSource.first(), remoteSource)
.filter(new Func1<Item, Boolean>() {
#Override
public Boolean call(Item item) {
return item!= null;
}
});
I've an observable like this
Observable.zip(observable, extObs, new Func2<List<UserProfile>, ArrayList<Extension>, UserProfile>() {
#Override
public UserProfile call(List<UserProfile> userProfiles, ArrayList<Extension> extensions) {
return userProfiles.get(0);
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).unsubscribeOn(Schedulers.io()).subscribe(new Subscriber<UserProfile>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(UserProfile userProfile) {
profileListener.onProfileSet(userProfile);
}
});
}
I need to pass the ArrayList in the methodprofileListener.onProfileSet(userProfile); as profileListener.onProfileSet(userProfile,extensions);
Is it possible to do so in zip or is there any other methods of rxjava to solve such type of problems?
You have to do exactly what cricket_007 suggested in the comment.
For example like this:
Create a class that would represent combined results of your observables:
class CombinedResults {
public UserProfile userProfile;
public List<Extension> extensions;
public CombinedResults(UserProfile userProfile, List<Extension> extensions) {
this.userProfile = userProfile;
this.extensions = extensions;
}
}
(Alternatively you could use Pair class)
Use an object of CombinedResults (or Pair) in your Observable.zip Func2.
Observable.zip(observable, extObs,
new Func2<List<UserProfile>, ArrayList<Extension>, CombinedResults>() {
#Override
public CombinedResults call(List<UserProfile> userProfiles, ArrayList<Extension> extensions) {
return new CombinedResults(userProfiles.get(0), extensions);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(new Subscriber<CombinedResults>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(CombinedResults combined) {
profileListener.onProfileSet(combined.userProfile, combined.extensions);
}
});
i'm new in Rx programming (and I'm having a lot of fun so far ^^).
I'm trying to transform a AsyncTask call into an Rx function.
My function :
Get all the installed apps
normalize the labels
sort everything alphabetically
arrange them by group of letter (it was a Multimap(letter, list of apps)) and pass the result to an adapter to display everything.
Here is how I'm doing so far with Rx :
Observable.from(getInstalledApps(getActivity(), false))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<ResolvedActivityInfoWrapper, ResolvedActivityInfoWrapper>() {
#Override
public ResolvedActivityInfoWrapper call(ResolvedActivityInfoWrapper act) {
// Normalize labels
act.setLabel(Normalizer.normalize(act.getLabel(getPackageManager()).replace(String.valueOf((char) 160), "").trim(), Normalizer.Form.NFD).replaceAll("\\p{M}", ""));
return act;
}
})
.toList()
.subscribe(new Observer<List<ResolvedActivityInfoWrapper>>() {
List<ResolvedActivityInfoWrapper> list;
#Override
public void onCompleted() {
Observable.from(list).groupBy(new Func1<ResolvedActivityInfoWrapper, String>() {
#Override
public String call(ResolvedActivityInfoWrapper input) {
//Get groups by letter
String label = input.getLabel(getPackageManager());
if (!TextUtils.isEmpty(label)) {
String firstChar = label.substring(0, 1);
if (pattern.matcher(firstChar).matches()) {
return firstChar.toUpperCase();
}
}
return "#";
}
}).subscribe(this); // implementation below
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<ResolvedActivityInfoWrapper> list) {
Collections.sort(list, new Comparator<ActivityInfoWrapper>() {
#Override
// Sort all the apps in the list, not sure it's a good way to do it
public int compare(ActivityInfoWrapper info1, ActivityInfoWrapper info2) {
return info1.getLabel(getPackageManager()).compareToIgnoreCase(info2.getLabel(getPackageManager()));
}
});
this.list = list;
}
});
Once I groupedBy letters, on complete I subscribe with this :
#Override
public void onCompleted() {
//display the apps
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(GroupedObservable<String, ResolvedActivityInfoWrapper> input) {
//For each list of apps by letter i subscribe with an observer that will handle those apps (observer code below)
input.subscribe(new TestObserver(input.getKey()));
}
Observer :
private class TestObserver implements Observer<ResolvedActivityInfoWrapper> {
List<ResolvedActivityInfoWrapper> list;
String letter;
public TestObserver(String letter) {
list = new ArrayList<>();
this.letter = letter;
}
#Override
public void onCompleted() {
adapter.addData(letter, list);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(ResolvedActivityInfoWrapper input) {
list.add(input);
}
}
Everything works correctly excpets for one problem : the observer's onCompleted are called not in the right order. So I got all my apps, sorted by letter, but the groups are nots displayed in the right order (C first, then Y, then M etc ...).
I guess there are plenty of errors in the code, can you help me with this probleme and maybe understanding how all this works please ?
Thanks
UPDATE :
Following the advices in the commentary section (thanks people), here is what I'm trying after normalizing the labels :
Observable.from(list).groupBy(new Func1<ResolvedActivityInfoWrapper, String>() {
#Override
public String call(ResolvedActivityInfoWrapper input) {
String label = input.getLabel(getPackageManager());
if (!TextUtils.isEmpty(label)) {
String firstChar = label.substring(0, 1);
if (pattern.matcher(firstChar).matches()) {
return firstChar.toUpperCase();
}
}
return "#";
}
})
.toSortedList(new Func2<GroupedObservable<String, ResolvedActivityInfoWrapper>, GroupedObservable<String, ResolvedActivityInfoWrapper>, Integer>() {
#Override
public Integer call(GroupedObservable<String, ResolvedActivityInfoWrapper> obs1, GroupedObservable<String, ResolvedActivityInfoWrapper> obs2) {
return obs1.getKey().compareToIgnoreCase(obs2.getKey());
}
})
.subscribe(new Observer<List<GroupedObservable<String, ResolvedActivityInfoWrapper>>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<GroupedObservable<String, ResolvedActivityInfoWrapper>> input) {
String test = input.get(0).getKey();
}
});
But it never goes into the Compare function.