I've created this long observable with several nested observables and two Zip operators.
My issue is that the outer most Zip operator never has its function called... in this case it's Func5(...).
I am able to debug the nested Zip operator and see the Func6(...) return the Map<String, Object> but then nothing happens. No onNext, onError, or onComplete from the subscriber.
What am I missing here?
The Zip operators return an observable so I assumed nesting was ok.
Observable.zip(
UserApi.doThis(someValue)
.flatMap(new Func1<String, Observable<typeA1>>() {
#Override
public Observable<typeA1> call(String photoId) {
return UserApi.getUserPhotoData(photoId);
}
}),
UserApi.doThat(),
UserApi.doSomething(settingName0),
InstitutionApi.getThis(someValue),
Observable.zip(
InstitutionApi.getInstitutionSetting(settingName1).onErrorReturn(new Func1<Throwable, typeB1>() {
#Override
public typeB1 call(Throwable throwable) {
return new typeB1(...);
}
}),
InstitutionApi.getInstitutionSetting(settingName2).onErrorReturn(new Func1<Throwable, typeB1>() {
#Override
public typeB1 call(Throwable throwable) {
return new typeB1(...);
}
}),
InstitutionApi.getInstitutionSetting(settingName3).onErrorReturn(new Func1<Throwable, typeB1>() {
#Override
public typeB1 call(Throwable throwable) {
return new typeB1(...);
}
}),
InstitutionApi.getInstitutionContentString(stringName1).onErrorReturn(new Func1<Throwable, typeB2>() {
#Override
public typeB2 call(Throwable throwable) {
return null;
}
}),
InstitutionApi.getInstitutionSetting(settingName4).onErrorReturn(new Func1<Throwable, typeB1>() {
#Override
public typeB1 call(Throwable throwable) {
return new typeB1(...);
}
}),
InstitutionApi.getInstitutionSetting(settingName5).onErrorReturn(new Func1<Throwable, typeB1>() {
#Override
public typeB1 call(Throwable throwable) {
return new typeB1(...);
}
}),
new Func6<typeB1, typeB1, typeB1, typeB2, typeB1, typeB1, Map<String, Object>>() {
#Override
public Map<String, Object> call(typeB1 r0, typeB1 r1, typeB1 r2,
typeB2 r3, typeB1 r4, typeB1 r5) {
Map<String, Object> map = new HashMap<>();
try {
// do things
} catch (Exception e) {
return null;
}
return map;
}
}),
new Func5<typeA1, typeA2, typeA3, typeA4, Map<String, Object>, EndReturnType>() {
#Override
public EndReturnType call(typeA1 r0, typeA2 r1, typeA3 r2, typeA4 r3, Map<String, Object> r4) {
EndReturnType ert = new EndReturnType ();
// do things
return ert;
}
});
As zsxwing mentioned in the comments, one of my functions was not returning a value due to a missing onNext function.
Related
this is a method written in RxJava
public Observable<String> method() {
return model.getOffers()
.filter(new Func1<Offers, Boolean>() {
#Override
public Boolean call(Offers offers) {
if (offers == null)
return false;
return offers.hasSuperOffer();
}
})
.flatMap(new Func1<Offers, Observable<Long>>() {
#Override
public Observable<Long> call(Offers offers) {
Long offerEndTime = offers.getRemainingTime();
if (offerEndTime == null) {
return Observable.empty();
}
AtomicLong remainingTimeSec;
Long currentTimeSec = System.currentTimeMillis() / 1000;
if (remainingTimeSec.get() == -1 && (offerEndTime > currentTimeSec)) {
remainingTimeSec.set(offerEndTime - currentTimeSec);
} else {
return Observable.empty();
}
return Observable.interval(1, TimeUnit.SECONDS)
.onBackpressureDrop()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.take(remainingTimeSec.intValue())
.doOnUnsubscribe(new Action0() {
#Override
public void call() {
}
})
.doOnCompleted(new Action0() {
#Override
public void call() {
}
})
.map(new Func1<Long, Long>() {
#Override
public Long call(Long elapsedTimeSec) {
return remainingTimeSec.getAndDecrement();
}
});
}
})
.map(new Func1<Long, String>() {
#Override
public String call(Long remainingTime) {
return DateUtils.getRemainingTimeStr(remainingTime);
}
});
}
I am trying to convert it to RxJava3 but some parameters have changed:
Func1 has been changed to Function
Action0 has been changed to Action
After I'm making the changes the following error appears at filter:
filter (io.reactivex.rxjava3.functions#io.reactivex.rxjava3.annotations.NonNull Predicate <? MyClass> in Observable cannot be applied to (anonymous.io.reactivex.rxjava3.functions.Function <MyClass.model.Offers.java.lang.Boolean>)
Can anyone help me?
Thank you!
I want to load the next item(s) when some other observable emits a new item (a trigger).
This is the code that emits the items:
public Observable<Item> get() {
return idApi.get().flatMap(new Function<List<String>, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(#NonNull List<String> ids) throws Exception {
return Observable.fromIterable(ids);
}
}).flatMap(new Function<String, ObservableSource<Item>>() {
#Override
public ObservableSource<Item> apply(#NonNull final String id) throws Exception {
return dataApi.get(id).map(new Function<Data, Item>() {
#Override
public Item apply(#NonNull Data data) throws Exception {
return new Item(data , id);
});
}
});
}
Trigger Observable:
RxView.clicks(view.findViewById(R.id.button_more)).debounce(500, TimeUnit.MILLISECONDS);
The only way I could get around this was using a Subject and holding a reference to a list of ids which wasn't elegant and didn't seem reactive.
Edit: This is my solution so far, but I had to subscribe to trigger event directly. I don't consider it elegant.
#Override
public Observable<Item> get(final Observable<Object> trigger) {
final PublishSubject<Item> subject = PublishSubject.create();
return idApi.get().flatMap(new Function<List<String>, ObservableSource<Queue<String>>>() {
#Override
public ObservableSource<Queue<String>> apply(#NonNull List<String> ids) throws Exception {
final Queue<String> q = new LinkedList<>(ids);
return Observable.just(q);
}
}).flatMap(new Function<Queue<String>, ObservableSource<Item>>() {
#Override
public ObservableSource<Item> apply(#NonNull final Queue<String> ids) throws Exception {
trigger.subscribeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Object>() {
#Override
public void accept(#NonNull Object o) throws Exception {
if (ids.size() > 0) {
final String id = ids.poll();
dataApi.get(id).map(new Function<Data, Item>() {
#Override
public Item apply(#NonNull Data data) throws Exception {
return new Item(data, id) l
}
}).subscribe(new Consumer<Item>() {
#Override
public void accept(#NonNull Item item) throws Exception {
subject.onNext(item);
}
});
} else {
subject.onComplete();
}
}
});
return subject;
}
});
}
Use zip
public Observable<Item> get(View v) {
return idApi.get().flatMap(new Function<List<String>, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(#NonNull List<String> ids) throws Exception {
return Observable.fromIterable(ids);
}
}).zipWith(RxView.clicks(v).debounce(500, TimeUnit.MILLISECONDS), (n, i) -> n))
.flatMap(new Function<String, ObservableSource<Item>>() {
#Override
public ObservableSource<Item> apply(#NonNull final String id) throws Exception {
return dataApi.get(id).map(new Function<Data, Item>() {
#Override
public Item apply(#NonNull Data data) throws Exception {
return new Item(data , id);
});
}
});
}
to get N items with each click
public Observable<Item> getN(View v, int nitems) {
return idApi.get().flatMap(new Function<List<String>, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(#NonNull List<String> ids) throws Exception {
return Observable.fromIterable(ids);
}
}).buffer(nitems).zipWith(RxView.clicks(v).debounce(500, TimeUnit.MILLISECONDS), (n, i) -> n))
.flatMap(new Function<List<String>, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(#NonNull final List<String> ids) throws Exception {
return Observable.from(ids)
}
}
)
.flatMap(new Function<String, ObservableSource<Item>>() {
#Override
public ObservableSource<Item> apply(#NonNull final String id) throws Exception {
return dataApi.get(id).map(new Function<Data, Item>() {
#Override
public Item apply(#NonNull Data data) throws Exception {
return new Item(data , id);
});
}
}
});
}
Edit: You will still have to use subscribeOn to make sure you are on the main thread for RXView.clicks and on the IO thread for any networking.
Not so good but it works:
your get method:
Observable<Item> get(final String id){
return Observable.defer(() -> {
dataApi.get(id).map(new Function<Data, Item>() {
#Override
public Item apply(#NonNull Data data) throws Exception {
return new Item(data , id);
});
})
}
your click:
private List<String> ids = {//your id s}
private int current = 0;
RxView.clicks(view).flatmap(ignore -> get(ids.get(current++)))
.subscribe(//your Observer)
I'd like recommend that answer with zipwith(), seems better than my answer.
I have an Observable<MoviesResponse>. My MovieResponse class contains a getResults() methods returning a List<Result>. This Result class has a getTitle() methods returning a String. I want to call the getTitle() methods of all my Result objects to get all the titles of my movies.
I achieved this with the code below using a foreach loop but I think there is a better way to do this by chaining RxJava operators, I just can't figure it out...
Subscription :
Observable<MoviesResponse> moviesResponseObservable = apiService.getTopRatedMoviesObservable(API_KEY);
subscription = moviesResponseObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<MoviesResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(MoviesResponse moviesResponse) {
List<Result> results = moviesResponse.getResults();
for (Result r:results) {
Log.d(LOG_TAG,r.getTitle());
}
}
});
Interface :
public interface ApiService {
#GET("movie/top_rated")
Observable<MoviesResponse> getTopRatedMoviesObservable(#Query("api_key") String apiKey);
}
You can use a flatmap to transform your observable into an Observable<Result> and then use map to turn that into Observable<String>, which you can then subscribe to.
moviesReponseObservable
.subscribeOn(Schedulers.io())
.flatMapIterable(new Function<MoviesResponse, Iterable<Result>>() {
#Override
public Iterable<Result> apply(#NonNull MoviesResponse moviesResponse) throws Exception {
return moviesResponse.getResults();
}
})
.map(new Function<Result, String>() {
#Override
public String apply(#NonNull Result result) throws Exception {
return result.getTitle();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
#Override
public void onNext(String s) {
Log.d("TAG", s);
}
/* ... */
});
I got the following error with #zsmb13 answer :
new Function : map (rx.functions.Func1) in
Observable cannot be applied to (anonymous
java.util.function.Function)reason:
no instance(s) of type variable(s) R exist so that Function conforms to Func1
Anyway this answer was very helpul I just replaced Function with Func1 and used call method.
subscription = moviesResponseObservable
.subscribeOn(Schedulers.io())
.flatMapIterable(new Func1<MoviesResponse, Iterable<Result>>() {
#Override
public Iterable<Result> call(MoviesResponse moviesResponse) {
return moviesResponse.getResults();
}
})
.map(new Func1<Result, String>() {
#Override
public String call(Result result) {
return result.getTitle();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String s) {
Log.d(LOG_TAG, s);
}
});
Is it possible to resubscribe an Observable and get the error?
The Observable<T> retry() method resubscribes the observable but it consumes the error.
final PublishSubject<Integer> observable = PublishSubject.create();
observable
.flatMap(new Func1<Integer, Observable<Integer>>() {
#Override
public Observable<Integer> call(final Integer integer) {
if (integer % 2 == 0) {
return Observable.just(integer);
} else {
return Observable.error(new Exception("int: " + integer));
}
}
})
.retry()
.subscribe(new Action1<Integer>() {
#Override
public void call(final Integer integer) {
Timber.i("integer: %d", integer);
}
},
new Action1<Throwable>() {
#Override
public void call(final Throwable throwable) {
Timber.e(throwable, "throwable");
}
},
new Action0() {
#Override
public void call() {
Timber.w("onCompleted");
}
});
Observable
.range(0, 10)
.delay(2, TimeUnit.MILLISECONDS)
.subscribe(new Action1<Integer>() {
#Override
public void call(final Integer integer) {
observable.onNext(integer);
}
},
new Action1<Throwable>() {
#Override
public void call(final Throwable throwable) {
observable.onError(throwable);
}
},
new Action0() {
#Override
public void call() {
observable.onCompleted();
}
});
The onError part of observable is never called because .retry() consumes the error.
What you're looking for is retryWhen(). This allows you to pass a Func1 which provides you with the Throwable, that means you can place your onError logic there instead.
This is a good article.
I try to get and render some data like below
raw data class
#Data #AllArgsConstructor class Category {
String name;
List<String> items;
}
presentation class
#Data #AllArgsConstructor class ViewModel {
public static final int TYPE_HEADER = 0;
public static final int TYPE_ITEM = 1;
int type;
String category;
String itemName;
}
and below code is request and transform subscribed data to presentation object.
Observable.create(new Observable.OnSubscribe<Category>() {
#Override public void call(Subscriber<? super Category> subscriber) {
subscriber.onNext(new Category("1", Lists.newArrayList("", "a", "b")));
subscriber.onNext(new Category("2", Lists.newArrayList("")));// this data does not output
subscriber.onNext(new Category("3", Lists.newArrayList("c", "", "d")));
subscriber.onNext(new Category("4", Lists.newArrayList("e", "f", "")));
}
}).flatMap(new Func1<Category, Observable<ViewModel>>() {
#Override public Observable<ViewModel> call(Category category) {
// TODO make this block to one line
// 1. clean response data and transform to ViewModel
List<ViewModel> cleanedItems = Lists.newArrayList(
Observable.from(category.getItems()).filter(new Func1<String, Boolean>() {
#Override public Boolean call(String s) {
return s != null && !s.isEmpty();
}
}).map(new Func1<String, ViewModel>() {
#Override public ViewModel call(String item) {
return new ViewModel(ViewModel.TYPE_ITEM, null, item);
}
}).toBlocking().toIterable());
if (cleanedItems.isEmpty()) {
// 2. case : skip
return Observable.empty();
} else {
// 3. case : add header and cleaned data
return Observable.concat(
Observable.just(new ViewModel(ViewModel.TYPE_HEADER, category.getName(), null)),
Observable.from(cleanedItems));
}
}
}).subscribe(new Action1<ViewModel>() {
#Override public void call(ViewModel viewModel) {
// render data
System.out.println(viewModel.toString());
}
});
output
ViewModel(type=0, category=1, itemName=null)
ViewModel(type=1, category=null, itemName=a)
ViewModel(type=1, category=null, itemName=b)
ViewModel(type=0, category=3, itemName=null)
ViewModel(type=1, category=null, itemName=c)
ViewModel(type=1, category=null, itemName=d)
ViewModel(type=0, category=4, itemName=null)
ViewModel(type=1, category=null, itemName=e)
ViewModel(type=1, category=null, itemName=f)
I try to write 1, 2, 3 (in comment) statement to 1 line (or more readable way), but I had no idea.
defaultIfEmpty operator seems not to use in this case.
Do anyone have any idea ?
How about this:
public static <T, R> Func1<? super T, ? extends Observable<? extends R>> ternary(
Func1<T, Boolean> predicate,
Func1<? super T, ? extends Observable<? extends R>> ifTrue,
Func1<? super T, ? extends Observable<? extends R>> ifFalse) {
return (item) -> predicate.call(item)
? ifTrue.call(item)
: ifFalse.call(item);
}
and you use it like this:
.map(CharSequence::toString)
.distinctUntilChanged()
.flatMap(ternary(Strings::notNullOrEmpty,
(kw) -> userRelationshipApi.searchFollowing(kw, null),
(kw) -> Observable.just(selectedUserListAdapter.getUsers())))
.subscribe(...)
AFAIK you cannot fullfil it using one line.
You have to determine if stream is empty and if not emit the items again. You can use cache to avoid double computations.
Observable.create(new Observable.OnSubscribe<Category>() {
#Override
public void call(Subscriber<? super Category> subscriber) {
subscriber.onNext(new Category("1", Lists.newArrayList("", "a", "b")));
subscriber.onNext(new Category("2", Lists.newArrayList("")));// this data does not output
subscriber.onNext(new Category("3", Lists.newArrayList("c", "", "d")));
subscriber.onNext(new Category("4", Lists.newArrayList("e", "f", "")));
subscriber.onCompleted();
}
}).flatMap(new Func1<Category, Observable<ViewModel>>() {
#Override
public Observable<ViewModel> call(Category category) {
final Observable<ViewModel> cleanedItems = Observable.from(category.getItems()).filter(new Func1<String, Boolean>() {
#Override
public Boolean call(String s) {
return s != null && !s.isEmpty();
}
}).map(new Func1<String, ViewModel>() {
#Override
public ViewModel call(String item) {
return new ViewModel(ViewModel.TYPE_ITEM, null, item);
}
}).cache();
return cleanedItems.isEmpty().flatMap(new Func1<Boolean, Observable<ViewModel>>() {
#Override
public Observable<ViewModel> call(Boolean aBoolean) {
if (aBoolean) {
return Observable.empty();
} else {
return Observable.concat(
Observable.just(new ViewModel(ViewModel.TYPE_HEADER, category.getName(), null)),
cleanedItems);
}
}
});
}
}).subscribe(new Action1<ViewModel>() {
#Override
public void call(ViewModel viewModel) {
// render data
System.out.println(viewModel.toString());
}
});
But instead of using rxjava whenever possible, sometimes you can use other api to make code simpler. In you case, you can for example use stream api:
.flatMap(new Func1<Category, Observable<ViewModel>>() {
#Override
public Observable<ViewModel> call(Category category) {
List<ViewModel> items = category.getItems().stream().filter(s -> s != null && !s.isEmpty()).map(s -> new ViewModel(ViewModel.TYPE_ITEM, null, s)).collect(Collectors.toList());
if (items.isEmpty()) {
return Observable.empty();
} else {
items.add(0, new ViewModel(ViewModel.TYPE_HEADER, category.getName(), null));
return Observable.from(items);
}
}
})
You can use operator filter() with switchIfEmpty(...)