"Join" a list of Observables in RxJava - android

I have been reading the documentation over and over again and I cannot find the right operator for my case:
return Observable.fromIterable(formIds)
.flatMap(new Function<String, Observable<List<Transmission>>>() {
#Override
public Observable<List<Transmission>> apply(String formId) {
return getFormTransmissions(formId);
}
})
.toList()
.toObservable()
.map(new Function<List<List<Transmission>>, List<Transmission>>() {
#Override
public List<Transmission> apply(List<List<Transmission>> lists) {
List<Transmission> transmissions = new ArrayList<>();
for (List<Transmission> transmissionList : lists) {
if (transmissionList != null) {
transmissions.addAll(transmissionList);
}
}
return transmissions;
}
});
As you can see the last map() I take all the observables and combine their content. I don't like this part as I feel there must be some rxjava function to do that but I cannot find one.
I will try to explain what the code does:
From a list of ids
For each one of them, get a list of transmissions for each id
Once all of those complete combine all the individual transmission lists into one. Any ideas?

So basically you need a simple map that can flatten the result. You can do this with flatMapIterable
return Observable.fromIterable(formIds)
.flatMap(new Function<String, Observable<List<Transmission>>>() {
#Override
public Observable<List<Transmission>> apply(String formId) {
return getFormTransmissions(formId);
}
}) // Observable<List<Transmission>>
.flatMapIterable(new Function<List<Transmission>, List<Transmission>>() {
#Override
public List<Transmission> apply(List<Transmission> list) {
return list;
}
}) // Observable<Transmission>
.toList() // Single<List<Transmission>>
.toObservable(); // Observable<List<Transmission>>
Even simpler with Java 8 features:
return Observable.fromIterable(formIds)
.flatMap(formId -> getFormTransmissions(formId)) // Observable<List<Transmission>>
.flatMapIterable(list -> list) // Observable<Transmission>
.toList() // Single<List<Transmission>>
.toObservable(); // Observable<List<Transmission>>
Alternatively you can just use flatMap in Java Stream:
return Observable.fromIterable(formIds)
.flatMap(formId -> getFormTransmissions(formId)) // Observable<List<Transmission>>
.toList() // Single<List<List<Transmission>>>
.toObservable() // Observable<List<List<Transmission>>>
.map(listOfList -> listOfList.stream().flatMap(Collection::stream)
.collect(Collectors.toList())); // Observable<List<Transmission>>

Related

Best way to get List from Observable in Rxjava

I'm just exploring Rxjava in one of my android application, and got stuck at one place, honestly speaking I'm very new to this library so don't mind if my question frustrate someone;-)
So I'm trying to access the Room Database using RxJava where I'm returning the Observable List, once I get this Observable I'm trying to use map operator to get a list of ids & query again the database, which again returns me the Observable List but the map operator expects List as a return type. How can I tackle this please suggest?
Below is the code snippet:
private void getAllPcbs() {
isLoading.setValue(true);
getCompositeDisposable().add(
getRepositoryManager().loadAllPcbDetails()
.flatMap((Function<List<PcbDetails>, ObservableSource<?>>) pcbDetails -> {
List<Long> pcbList = new ArrayList<>();
for (PcbDetails details : pcbDetails)
pcbList.add(details.getPcbId());
return getRepositoryManager().loadAllPcbs(pcbList);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccess, this::onError)
);
}
private void onError(Throwable throwable) {
isLoading.setValue(false);
}
private void onSuccess(Object o) {
isLoading.setValue(false);
pcbList.setValue((List<Pcb>) o);
}
public interface DbHelper {
Observable<List<PcbDetails>> loadAllPcbDetails();
Observable<List<Pcb>> loadAllPcbs(List<Long> pcbIdList);
}
Go like
getRepositoryManager().loadAllPcbDetails()
.flatMapIterable {
listPcbDetail-> listPcbDetail
// listPcbDetail is ArrayList<PcbDetails>
// Converts your list of ids into an Observable
// which emits every item in the list
}
.flatMap { pcbDetail ->
// pcbDetail is PcbDetails
getRepositoryManager().loadAllPcbs(pcbDetail.pcbIdList)
}.subscribe { listPcb ->
// listPcb is ArrayList<Pcb>
}

How I can use socket.io and rxjava 2 android

I develop an app with mpv (mosby3) + socket.io.
I want to use rxjava 2 to relate provider and repository.
I have CategoryManager
public class CategoryManager {
private List<Category> list = null;
...
}
If list not null i can do it
public Single<List<Category>> getList() {
return Single.just(this.list);
}
But if I need load the list I have do it async like a it
socket.on("category", (data) -> {
Type founderListType = new TypeToken<ArrayList<Category>>() {}.getType();
list = gson.fromJson(data[0].toString(), founderListType);
// here i need to generate event to single subscriber
})
I think i should use to
public Single<List<Category>> getList(int count) {
return Single.create(s -> {
if (list == null) {
socket.emit("category");
// i need async load list
} else {
s.onSuccess(list.subList(0, count));
}
});
}
And CategoryPresenter should have code like
disposable.add(session.getCategoryManager()
.getList(5)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
if (isViewAttached()) {
getView().setData(data);
}
}, throwable -> {
throwable.printStackTrace();
})
);
}
I think i can keep subscribers in class property
private List<SingleEmmiter> subscribers;
and remove subscribers in setCancellable method, but i don't think it good idea.
Help me please :)

Retrofit & RxJava multiple requests complete

I need to do:
Request 2 lists of news from different websites
Combine results from requests
Sort items by date
Get 10 newest news
Save them
Show complete message
For example, I have this two observables:
Observable<RegionalNews> regionalNews;
Observable<NationalNews> nationalNews;
public interface NewsNationalService {
#GET("news/national")
Observable<News> getNationalNews();
}
public interface NewsRegionalService {
#GET("news/regional")
Observable<News> getRegionalNews();
}
You can use zip operator to call 2 requests async and save or process their data on response.
For example.
Below are two Observable
Observable<ResponseOne> responseOneObservable = getRetrofitClient().getDataOne()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Observable<ResponseTwo> responseTwoObservable = getRetrofitClient().getDataTwo()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Using zip operator on above two Observable as below.
Observable<ArrayList<TestData>> testDataObservable = Observable.zip(responseOneObservable, responseTwoObservable, new Func2<ResponseOne, ResponseTwo, ArrayList<TestData>>() {
#Override
public ArrayList<TestData> call(ResponseOne responseOne, ResponseTwo responseTwo) {
ArrayList<TestData> testDataList = new ArrayList();
// process data from response responseOne & responseTwo
return testDataList;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ArrayList<TestData>>() {
#Override
public void onNext(ArrayList<TestData> testDataList) {
}
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted" );
// you can show alert here or do something when completed
}
#Override
public void onError(Throwable t) {
Log.d(TAG, "onError Throwable: " + t.toString() );
}
});
If you don't want to do something specific with the combined results, then merge() is enough:
Observable<RegionalNews> regionalNews = ...;
Observable<NationalNews> nationalNews = ...;
Observable
.merge(regionalNews, nationalNews)
.ignoreElements()
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(() -> { /* show alert */ })
.subscribe()
Well it depends, as always. Do you need to process the returned values down the chain, or just save it?
In this implementation I use Single and Completable. You subscribe to the completable and you will get notified when both Singles finished.
#Test
public void name() throws Exception {
TestScheduler testScheduler = new TestScheduler();
Single<Long> request1 = Single.timer(1000, TimeUnit.MILLISECONDS, testScheduler)
.doOnSuccess(aLong -> {
System.out.println("save to db.");
});
Single<Long> request2 = Single.timer(500, TimeUnit.MILLISECONDS, testScheduler)
.doOnSuccess(aLong -> {
System.out.println("save to db.");
});
Completable completable = Single.zip(request1, request2, (aLong, aLong2) -> aLong).toCompletable();
TestObserver<Void> test = completable.test();
testScheduler.advanceTimeBy(1010, TimeUnit.MILLISECONDS);
test.assertComplete();
}
You also can use flatMapCompletable instead of doOnSuccess
#Test
public void name() throws Exception {
TestScheduler testScheduler = new TestScheduler();
Completable request1 = Single.timer(1000, TimeUnit.MILLISECONDS, testScheduler)
.flatMapCompletable(this::saveToDb);
Completable request2 = Single.timer(500, TimeUnit.MILLISECONDS, testScheduler)
.flatMapCompletable(this::saveToDb);
// need to cheat here, becuase completeable does not provide zip
Completable completable = Single.zip(request1.toSingle(() -> 1), request1.toSingle(() -> 1), (aLong, aLong2) -> aLong)
.toCompletable();
TestObserver<Void> test = completable.test();
testScheduler.advanceTimeBy(1010, TimeUnit.MILLISECONDS);
test.assertComplete();
}
private Completable saveToDb(long value) {
return Completable.complete();
}
zip is the way to combine observables. Combining their results is just a consequence.
If you want to wait for both observables to finish (complete), the easiest way is to use zip. You just don't have to use the results of your requests in the combining function. Just use this function as a way to emit something different when both of those calls finish. When this function emits an item:
[...] do something when all requests completed (show alert for example)
For example like this (executing someOtherCall when both of those requests finish):
Observable<Integer> obs1 = ...;
Observable<Long> obs2 = ...;
Observable.zip(obs1, obs2, new Func2<Integer, Long, String>() {
#Override
public String call(Integer integer, Long aLong) {
return "something completely different";
}
}).flatMap(new Func1<String, Observable<Float>>() {
#Override
public Observable<Float> call(String s) {
return performSomeOtherCall();
}
}).subscribe(...);

Filter list of Object in Rxjava

I want to filter List<Object> based on query from user and then return List<Object> to him/her.I found out how to filter items But the problem is I don't know how return List<Object>. I also see some approach which iterate and call flatMap each time But I didn't think it's an elegant way.
This is my last attempt:
Observable.from(my_list_of_object)
.debounce(500, TimeUnit.MILLISECONDS)
.filter(new Func1<MyObject, Boolean>() {
#Override
public Boolean call(MyObject o) {
return o.getName().contains(query); //filtering
}
})
.observeOn(Schedulers.computation())
//problem is here and I dont know how
//to convert filtered Item to list
Just use toList() operator.
Check the documentation.
Observable.from(my_list_of_object)
.debounce(500, TimeUnit.MILLISECONDS)
.filter(new Func1<MyObject, Boolean>() {
#Override
public Boolean call(MyObject o) {
return o.getName().contains(query); //filtering
}
})
.toList()
.observeOn(Schedulers.computation())
You can find more extensive list of aggregate operators here.

How to create an Rx Search view that will filter a recycler view with a query

I want to implement a Rx Search view that will filter a recycler view with the string and also check the local Realm db and make a retrofit request, then combines all the results without duplicates ?? So, in other words: I would like to on text change event in a search view, to use the input string to make a network request, a db query and combine with results without duplicates
This is my initial code:
RxSearchView.queryTextChanges(searchView)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.flatMap(charSequence -> userListPresenter.search(charSequence))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.onErrorResumeNext(Observable.empty())
.subscribe((Action1) response -> userListPresenter.showSearchResult((List<UserModel>) response));
The userListPresenter.search(charSequence)) should return an observable of the concatenated response without duplicates, thanks :)
userListPresenter.search(charSequence)):
Observable.create(subscriber -> {
if (Utils.isNetworkAvailable(getContext())) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(restApi.search(query));
subscriber.onCompleted();
}
}).mergeWith(
Observable.create(subscriber -> {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(realmManager.getWhere(query));
subscriber.onCompleted();
}
})).collect(HashSet::new, HashSet::add)
.flatMap(Observable::from);
if I correctly understood your question. There is good way to combine the emissions of multiple Observables, look at zip method.
So, Your search method would be look like that:
public Observable<List<MyItem>> search(CharSequence query) {
return Observable.zip(
observeNetwork(query),
observeRealm(query),
new Func2<List<MyItem>, List<MyItem>, List<MyItem>>() {
#Override
public List<MyItem> call(List<MyItem> networkResult, List<MyItem> databaseResult) {
return merge(networkResult,databaseResult);
}
}
);
}
private Observable<List<MyItem>> observeRealm(CharSequence searchString) {
return Observable.create( /* do your realm stuff */);
}
private Observable<List<MyItem>> observeNetwork(CharSequence searchString) {
return return Observable.create( /* do your network stuff */);
}
private List<MyItem> merge(List<MyItem> networkResult, List<MyItem> databaseResult) {
List<MyItem> result = new ArrayList<>();
result.addAll(databaseResult);
for(MyItem newItem : networkResult){
if(!databaseResult.contains(newItem)){
result.add(newItem);
}
}
return result;
}

Categories

Resources