I'm using RxJava 2 on a new project (I've been using RxJava 1 for a long time) and I have some problems with using a flatMap (or maybe flatMapSingle?).
There seems to be something I'm missing in the whole concept.
mObjectManager.getAllObjects returns a AsyncProcessor<List<Object>>.
(I replaced the actual Class name with 'Object').
Disposable subscription = mObjectManager.getAllObjects()
.flatMapSingle(new Function<List<Object>, SingleSource<Object>>() {
#Override
public SingleSource<Object > apply(#io.reactivex.annotations.NonNull List<Object> objects) throws Exception {
// TODO WHAT GOES HERE?!
}
}).filter(new Predicate<Object>() {
#Override
public boolean test(#io.reactivex.annotations.NonNull Object object) throws Exception {
return TextUtils.isEmpty(mSearchTerm) || object.name.toLowerCase().contains(mSearchTerm.toLowerCase());
}
}).toSortedList(new Comparator<Object>() {
#Override
public int compare(Object c1, Object c2) {
return c1.name.compareTo(c2.name);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<Object>>() {
#Override
public void accept(#io.reactivex.annotations.NonNull List<Object> objects) throws Exception {
processObjects(objects);
}
});
I'm wondering how I can transform the list to a SingleSource?
How is the flatMap being used in RxJava 2?
Okay, I found the answer after all.
Flowable.fromIterable does the trick!
...
.flatMap(new Function<List<Object>, Publisher< Object >>() {
#Override
public Publisher< Object > apply(#io.reactivex.annotations.NonNull List< Object > objects) throws Exception {
return Flowable.fromIterable(objects);
}
})
Related
I'm just starting out with RxJava and trying a sample project.
What I'm trying to achieve is
-> Get an object ->
which contains a list of sub-objects -> Check if the sub-list satisfies a predicate condition -> and emit the sub-objects if satisfies
This is my POJO
public class UpComingMovies {
#SerializedName("results")
private List<Movies> results;
}
public class Movies {
#SerializedName("overview")
private String overview;
#SerializedName("original_language")
private String originalLanguage;
}
So, from what I understand is that I can use flatMap and transform the item to multiple observables and then use filter saying give me movies which has originalLanguage.equals("en")
This is what I have tried to do
#GET("movie/upcoming")
Observable<UpComingMovies> getUpComingMovies(#Query("api_key") String apiKey, #Query("page") String page);
private final CompositeDisposable disposables = new CompositeDisposable();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
disposables.add(RetrofitConnection.getService()
.getUpComingMovies(Config.API_KEY, "1")
.flatMapIterable(new Function<UpComingMovies, Iterable<Movies>>() {
#Override
public Iterable<Movies> apply(#NonNull UpComingMovies upComingMovies) throws Exception {
// does not compile - needs an iterable
return upComingMovies.getResults().iterator();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<UpComingMovies>() {
#Override
public void onNext(UpComingMovies upComingMovies) {
Log.d(getClass().getName(), upComingMovies.toString());
}
#Override
public void onError(Throwable e) {
Log.d(getClass().getName(), e.getMessage());
}
#Override
public void onComplete() {
Log.d(getClass().getSimpleName(), "onComplete");
}
}));
}
However, it does not compile.
Cleary I do not know how to do this, any help is appreciated
Your filter shouldn't compile because of the previous function that generates an iterable of UpcomingMovies. In other words, that filter is expecting an observable of movies, but the previous function is mapping to an iterable of UpcomingMovies
You seem to be wanting to map UpcomingMovies into a List / Iterable of Movies
In order to do that, try starting with this Function definition
flatMapIterable(new Function<UpComingMovies, Iterable<Movies>>()
Then, you can use getResults() of the UpcomingMovies object parameter, to get the sublist, return it, then that's passed to the subsequent filter operation
In code,
.flatMapIterable(new Function<UpComingMovies, Iterable<Movies>>() {
#Override
public Iterable<Movies> call(UpComingMovies upcomingMovies) {
return upcomingMovies.getResults();
}
})
Also, if you enable Java 8 compilation with lambdas, then the code is much simpler... Reduced to one line
.flatMapIterable(UpComingMovies::getResults)
I'm just starting out with RxJava and trying a sample project.
What I'm trying to achieve is
-> Get an object ->
which contains a list of sub-objects -> Check if the sub-list satisfies a predicate condition -> and emit the sub-objects if satisfies
This is my POJO
public class UpComingMovies {
#SerializedName("results")
private List<Movies> results;
}
public class Movies {
#SerializedName("overview")
private String overview;
#SerializedName("original_language")
private String originalLanguage;
}
So, from what I understand is that I can use flatMapIterable and transform the item to multiple observables and then use filter saying give me movies which has originalLanguage.equals("en")
This is what I have tried to do
#GET("movie/upcoming")
Observable<UpComingMovies> getUpComingMovies(#Query("api_key") String apiKey, #Query("page") String page);
private final CompositeDisposable disposables = new CompositeDisposable();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
disposables.add(RetrofitConnection.getService()
.getUpComingMovies(Config.API_KEY, "1")
.flatMapIterable(new Function<UpComingMovies, Iterable<Movies>>() {
#Override
public Iterable<Movies> apply(#NonNull UpComingMovies upComingMovies) throws Exception {
// no instance(s) of the type variable(s) U exist so that the Observable<U> conforms to a disposable
return upComingMovies.getResults();
}
})
.filter(movies -> movies.getVoteCount() > 200).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<Movies>() {
#Override
public void onNext(#NonNull Movies movies) {
}
#Override
public void onError(#NonNull Throwable e) {
}
#Override
public void onComplete() {
}
}));
}
However, it does not compile.
Cleary I do not know how to do this, any help is appreciated
The reason it doesn't compile is because you're returning an observable and your function signature is expecting an iterable:
new Function<UpComingMovies, Iterable<Movies>>() {
#Override
public Iterable<Movies> apply(#NonNull UpComingMovies upComingMovies) throws Exception {
// List<Movies> is iterable
return upComingMovies.getResults();
}
}
If you fix it this way, then you can do
...flatMapIterable(...)
.filter(new Predicate<Movies>() {
#Override
public boolean test(Movies movies) throws Exception {
return movies.originalLanguage.equals("en");
}
})...
I don't know if it is exactly what you're trying to do but replace
return Observable.fromIterable(upComingMovies.getResults());
with
return upComingMovies.getResults();
if you want your project to compile
If I understood your code correctly, you get a single result of type UpcomingMovies (which is why you should use Single here and not Observable) and then flatten that to multiple emissions of type Movies. That's why your subscribe function doesn't work: it expects UpcomingMovies but gets Movies.
Something like this is probably what you want:
#GET("movie/upcoming")
Single<UpComingMovies> getUpComingMovies(#Query("api_key") String apiKey, #Query("page") String page);
RetrofitConnection.getService()
.getUpComingMovies(Config.API_KEY, "1")
.flatMapIterable((UpComingMovies upComingMovies) -> {
return upComingMovies.getResults();
})
.filter((Movies movie) -> {
return movie.originalLanguage.equals("en");
})
.subscribe((Movies movie) -> {
Log.d("", movie.toString());
});
The code below....
observes two integer streams from an Android ViewPager (pageSelected, scrollState).
combines & filters the streams looking for a certain combination.
if found, executes a network call.
consumes the result of network call.
Questions
1. Is flatmap() / concatmap() the only way to chain Observables for the code below? (To avoid nesting subscribe() call)
2. Is the use of observeOn() to switch threads correct / intuitive in the flat code or is there a better way to do this? (DataTools call is asynchronous)
I found applying the correct threading between Observable, Operator, and Subscriber confusing when using the nested code.
Flat version
public void loadNextAtTest4(final int pagesRemaining) {
ObservableCombineLatest.combineLatest(pageSelectObs, scrollStateObs, new BiFuncOnlyIdlePages()).filter(new Predicate<Integer>() {
#Override
public boolean test(#NonNull Integer page) throws Exception {
return page > -1 && pagesRemaining == page;
}
}).observeOn(Schedulers.io())
.flatMap(new Function<Integer, Observable<List<PageDescriptor>>>() {
#Override
public Observable<List<PageDescriptor>> apply(#NonNull Integer integer) throws Exception {
return DataTools.getNextPageBase(mRedClient, mFpxClient).toObservable();
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<PageDescriptor>>() {
#Override
public void accept(#NonNull List<PageDescriptor> pageDescriptors) throws Exception {
mCPAdapter.appendDataset(pageDescriptors);
}
});
}
Nested version
public void loadNextAtTest2(final int pagesRemaining) {
ObservableCombineLatest.combineLatest(pageSelectObs, scrollStateObs, new BiFuncOnlyIdlePages()).filter(new Predicate<Integer>() {
#Override
public boolean test(#NonNull Integer page) throws Exception {
return page > -1 && pagesRemaining == page;
}
}).observeOn(Schedulers.io())
.subscribe(new Consumer<Integer>() {
#Override
public void accept(#NonNull Integer page) throws Exception {
DataTools.getNextDataBase(mRedClient, mFpxClient)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<PageDescriptor>>() {
#Override
public void accept(#NonNull List<PageDescriptor> pageDescriptors) throws Exception {
mCPAdapter.appendDataset(pageDescriptors);
}
});
}
});
}
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;
}
});
So this is what I have so far...
public Observable<List<Integer>> getIds() {
return Observable.create(new Observable.OnSubscribe<List<Integer>>() {
#Override
public void call(Subscriber<? super List<Integer>> subscriber) {
try {
subscriber.onNext(mSource.getIds());
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
This works fine and gets me the list of ids for the objects I'm trying to create
Then I need a function that returns a subscription to an Observer with a list of objects. I need to make a separate api call to getObject(int id) to get each of these objects.
public Subscription getObjectList(Observer<List<Object>> observer) {
return mService.getIds()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// here is where i get lost...
.map(new Func1<Observable<Integer>, Observable<List>>)
Any help would be appreciated thanks.
Alright so since your getIds() method returns an Observable<List<Integer>> the proper way to create your Func1 in map would be
.map(new Func1<List<Integer>, List<Object>>() {
It takes in a list of integers and will emit a list of objects. I believe that is what you're trying to achieve. Your final code could look something like so
public Subscription getObjectList(Observer<List<Object>> observer) {
return getIds()
.map(new Func1<List<Integer>, List<Object>>() {
#Override
public List<Object> call(List<Integer> integers) {
List<Object> objects = new ArrayList<Object>();
for (Integer id : integers) {
// Here's where you map each ids to an object and add it to the list
objects.add(getObject(id));
}
return objects;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
// Add your own code here to load the object based on the id.
private Object getObject(Integer id) {
return id;
}
// Replace with your own getIds from above
public Observable<List<Integer>> getIds() {
return Observable.from(1, 2, 3).toList();
}
This should help you get started, let me know if there's something which you don't understand.