Android RxJava joining lists - android

Trying to understand all that RxJava stuff. I was doing following example:
private Observable<List<String>> query1() {
List<String> urls = new ArrayList<>();
urls.add("1");
urls.add("2");
urls.add("3");
urls.add("4");
return Observable.just(urls);
}
private Observable<List<String>> query2() {
List<String> urls = new ArrayList<>();
urls.add("A");
urls.add("B");
urls.add("C");
urls.add("D");
return Observable.just(urls);
}
and then tried to join two lists:
Observable.zip(
query1(),
query2(),
new Func2<List<String>, List<String>, Observable<String>>() {
#Override
public Observable<String> call(List<String> a1, List<String> a2) {
List<String> list = new ArrayList<>();
list.addAll(a1);
list.addAll(a2);
return Observable.from(list);
}
})
.subscribe(new Action1<String>() { // <-- It says, cannot resolve method subscribe
#Override
public void call(String string) {
String text = testTextView.getText().toString();
testTextView.setText(text + "\n" + string);
}
});
What I'm doing wrong? I was expecting to get in my view
1
2
3
4
A
B
C
D
EDIT1 I ended with the following answer:
Observable.zip(
query1(),
query2(),
new Func2<List<String>, List<String>, List<String>>() {
#Override
public List<String> call(List<String> a1, List<String> a2) {
List<String> list = new ArrayList<>();
list.addAll(a1);
list.addAll(a2);
return list;
}
})
.flatMap(new Func1<List<String>, Observable<String>>() {
#Override
public Observable<String> call(List<String> urls) {
return Observable.from(urls);
}
})
.subscribe(new Action1<String>() {
#Override
public void call(String string) {
String text = testTextView.getText().toString();
testTextView.setText(text + "\n" + string);
}
});
EDIT2 concat solution as suggested by ihuk would be much better in this case. Appreciate for all the answers.

I believe the operators you are looking for are concat or merge.
Concat will emit the emissions from two or more Observables without interleaving them.
Merge on the other hand will combine multiple observables by merging their emissions.
For example:
String[] numbers = {"1", "2", "3", "4"};
String[] letters = {"a", "b", "c", "d"};
Observable<String> query1 = Observable.from(numbers).delay(1, TimeUnit.SECONDS);
Observable<String> query2 = Observable.from(letters);
Observable
.concat(query1, query2)
.subscribe(s -> {
System.out.printf("-%s-" + s);
});
Will print -1--2--3--4--a--b--c--d-. If you replace concat with merge the result will be -a--b--c--d--1--2--3--4-.
Zip operator will combine multiple Observables together via specified function. For example
Observable
.zip(query1, query2, (String n, String l) -> String.format("(%s, %s)", n, l))
.subscribe(s -> {
System.out.printf("-%s-", s);
});
Will output -(1, a)--(2, b)--(3, c)--(4, d)-.

thats because you are trying to return Observable from zip function, but then you pass Action<String>
Observable.zip(
query1(),
query2(),
new Func2<List<String>, List<String>, List<String>>() {
#Override
public List<String> call(List<String> a1, List<String> a2) {
List<String> list = new ArrayList<>();
list.addAll(a1);
list.addAll(a2);
return list;
}
})
.subscribe(
(string)-> System.out.println(string)
);

Observable<List<String>> query1(){
List<String> s = new ArrayList<>();
s.add("1");s.add("1");s.add("1");
return Observable.just(s);
}
Observable<List<String>> query2(){
List<String> s = new ArrayList<>();
s.add("1");s.add("1");s.add("1");
return Observable.just(s);
}
void HelloRx(){
Map<String,String> map2=new LinkedHashMap<>();//pick the result you want to return Here !
Observable.zip(query1(),//Observable Method 1
query2(),//Observable Method 2
(result1,result2)->{
for(String s : result1){//result1 is the value returned by query1 , result2 ...u know.
//do whatever you want
//map.put(......)
}
return null;
})
.subscribeOn(BackgroundSchedulers.getMultiThreadInstance())
.observeOn(AndroidSchedulers.mainThread())
.doOnCompleted(() -> {
//Do Something when finish for example transmit data to your adapter
})
.subscribe();
}

Apparently to join two lists into one list, you can do Observable.concat() on their Observable.from()s and then call Observable.toList().
RealmResults<Cat> equalTo;
RealmResults<Cat> beginsWith;
#Override
public void onViewRestored() {
compositeSubscription = new CompositeSubscription();
equalTo = realm.where(Cat.class).equalTo("field", filterString).findAllSorted("field");
beginsWith = realm.where(Cat.class).beginsWith("field", filterString).findAllSorted("field");
compositeSubscription.add(realm.asObservable()
.switchMap(new Func1<Realm, Observable<Cat>>() {
#Override
public Observable<Cat> call(Realm realm) {
return Observable.concat(Observable.from(equalTo), Observable.from(beginsWith));
}
})
.toList()
.subscribe(cats -> {
// update adapter with List<Cat>
}));

Related

Observable.fromCallable() does not Return data from Arraylist in Android using RXJava

I am trying to use RXjava to get data from an Arraylist in Android
public ArrayList<String> SelecIDValueGetterObservable(Context mContext) {
ArrayList<String> SelectedIds = new ArrayList<>();
CompositeDisposable mCompositeDisposable = new CompositeDisposable();
mCompositeDisposable.add(Observable.fromCallable(() -> SelecetIDValuegetter(mContext))
.subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<ArrayList<String>>() {
#Override
public void onNext(ArrayList<String> IdObsList) {
Toast.makeText(mContext,
"size " + IdObsList.size(), Toast.LENGTH_LONG).show();
for (int i = 0; i < IdObsList.size(); i++) {
SelectedIds.add(IdObsList.get(i));
}
}
#Override
public void onError(Throwable e) {
FirebaseCrashlytics.getInstance().recordException(e);
}
#Override
public void onComplete() {
// mCompositeDisposable.dispose();
}
})
);
return SelectedIds;
}
the SelecetIDValuegetter(mContext) is Like this
public ArrayList<String> SelecetIDValuegetter(Context mContext) {
ArrayList<String> SelectedIds = new ArrayList<>();
SelectedIds .add("A");
SelectedIds .add("B");
SelectedIds .add("C");
SelectedIds .add("D");
return SelectedIds ;
}
If SelecetIDValuegetter emits data normally, hwoever It deosnot work In Rxjava, how can we do it
If you want to use Observable, you should not return list directly as Observable is async, instead return Observable and subscribe to receive the data
public Observable<List<String>> SelecIDValueGetterObservable(Context mContext) {
return Observable.fromCallable(() -> SelecetIDValuegetter(mContext));
}
//at receiver end
SelecIDValueGetterObservable(this)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(IdObsList -> {
//ypu will get the list here
});
On a side note, Not sure about your need for using Observable in between SelecetIDValuegetter, you can directly return the list instead of using an Observable as you are not doing heavy work in method SelecetIDValuegetter AFAIK.

Trigger a second query from LiveData and merge results (Firestore)?

I have two collections: Users and Books. I need to get the results of both of them whether Users OR Books is updated and then merge the results together into a LinkedHashMap to use as a listView menu.
I thought a MediatorLiveData would be the way to go, but if I put the query of Users and the Query of Books in then I get null from one of the two LiveData objects because only one or the other fires. I thought maybe if one of them fires, then perhaps I have a query run inside each addSource() in the MediatorLiveData, but I'm not sure if that's the way to go.
My post regarding the MediatorLiveData is here:
Using MediatorLiveData to merge to LiveData (Firestore) QuerySnapshot streams is producing weird results
My two queries and LiveData objects are as follows:
//getUsers query using FirebaseQueryLiveData class
private Query getUsersQuery() {
FirebaseAuth mAuth = FirebaseAuth.getInstance();
adminID = mAuth.getUid();
query = FirebaseFirestore.getInstance().collection("admins")
.document(adminID)
.collection("users")
return query;
}
private FirebaseQueryLiveData usersLiveData = new FirebaseQueryLiveData(getUsersQuery());
//getBooks query using FirebaseQueryLiveData class
private Query getBooksQuery () {
FirebaseGroupID firebaseGroupID = new FirebaseGroupID(getApplication());
groupID = firebaseGroupID.getGroupID();
query = FirebaseFirestore.getInstance().collection("books")
.whereEqualTo("groupID", groupID)
return query;
}
private FirebaseQueryLiveData booksLiveData = new FirebaseQueryLiveData(getBooksQuery());
Somehow when Users updates, I need to get the data of Books as well and then merge them, but I also need this to happen if Books updates and then get the data of Users and merge them.
Any ideas would be greatly appreciated.
Additional Note/Observation
Okay, so I'm not completely ruling out a MediatorLiveData object. Certainly it allows me the listening of two different LiveData objects within the same method, however, I don't want to merge the two of them directly because I need to act on each liveData object individually. So as an example: usersLiveData fires because we create or modify a user, then I need to query books, get the results and merge users and books etc.
Below is my MediatorLiveData as it currently stands:
//MediatorLiveData merge two LiveData QuerySnapshot streams
private MediatorLiveData<QuerySnapshot> usersBooksLiveDataMerger() {
final MediatorLiveData<QuerySnapshot> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
mediatorLiveData.setValue(querySnapshot);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
mediatorLiveData.setValue(querySnapshot);
}
});
return mediatorLiveData;
}
Right now it's returning null results of the other LiveData source. Instead I need to query then merge. Any ideas on how to do this? There isn't much out there on this very thing.
I tried putting a query inside a Function that is called using a Transformations.map() but because of it be an asynchronous call, the return statement is being called before the query finishes.
Here's my attempt at the Function:
private class ListenUsersGetBooks implements Function<QuerySnapshot, LinkedHashMap<User, List<Book>>> {
#Override
public LinkedHashMap<User, List<Book>> apply(final QuerySnapshot input) {
userBookList = new LinkedHashMap<>();
getBooksQuery().get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
List<User> users = input.toObjects(User.class);
List<Book> books = task.getResult().toObjects(Book.class);
Log.d(TAG, "USERLIST! " + users);
Log.d(TAG, "BOOKLIST! " + books);
for (User user : users) {
bookList = new ArrayList<>();
for (Book book : books) {
if (user.getUserID().equals(book.getUserID())
&& book.getBookAssigned()) {
bookList.add(book);
}
else if (user.getAllBookID().equals(book.getBookID())) {
bookList.add(book);
}
}
userBookList.put(user, bookList);
}
Log.d(TAG,"OBSERVE userBookList: " + userBookList);
}
});
return userBookList;
}
}
Here's a simple version of what you could do, I hope it makes sense.
You're close with the MediatorLiveData. Instead of MediatorLiveData<QuerySnapshot> you probably want to use a custom object like this:
class MyResult {
public QuerySnapshot usersSnapshot;
public QuerySnapshot booksSnapshot;
public MyResult() {}
boolean isComplete() {
return (usersSnapshot != null && booksSnapshot != null);
}
}
Then in your observers, do something like this:
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = mediatorLiveData.getValue();
current.usersSnapshot = querySnapshot;
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = mediatorLiveData.getValue();
current.booksSnapshot = querySnapshot;
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
Then when you observe the combined live data:
usersBooksLiveDataMerger().observe(new Observer<MyResult>() {
#Override
public void onChanged(#Nullable MyResult result) {
if (result == null || !result.isComplete()) {
// Ignore, this means only one of the queries has fininshed
return;
}
// If you get to here, you know all the queries are ready!
// ...
}
});
I think I solved it. We were declaring a new MyResult object in each mediatorLiveData.addSource() method. Which meant that we were getting a new object for each QuerySnapshot so we would never get them to merge with each other.
Here's the update to MediatorLiveData:
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
final MyResult current = new MyResult();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
current.setUsersSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
current.setBooksSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
Now I'm getting users and books in the observer in Activity! Now the only thing I need to do is transform (merge the data) into a LinkedHashMap, but I think I got that figured out. Thanks Sam!
So this is where I am with your suggestions Sam.
I added getter and setter methods to the MyResult class as it wasn't giving me access to the member variables in the observer otherwise:
public class MyResult {
QuerySnapshot usersSnapshot;
QuerySnapshot booksSnapshot;
//default constructor
public MyResult() {
}
public QuerySnapshot getUsersSnapshot() {
return usersSnapshot;
}
public void setUsersSnapshot(QuerySnapshot usersSnapshot) {
this.usersSnapshot = usersSnapshot;
}
public QuerySnapshot getBooksSnapshot() {
return booksSnapshot;
}
public void setBooksSnapshot(QuerySnapshot booksSnapshot) {
this.booksSnapshot = booksSnapshot;
}
public boolean isComplete() {
return (usersSnapshot != null && booksSnapshot != null);
}
}
Here's the MediatorLiveData and get method. I changed the MyResult class initialization to = new MyResult(); thinking there was an issue with using mediatorLiveData.getValue(); as the initialization and get method.
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = new MyResult();
current.setUsersSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = new MyResult();
current.setBooksSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
public MediatorLiveData<MyResult> getUsersBooksLiveDataMerger() {
return usersBooksLiveDataMerger();
}
And finally the observer:
mainViewModel.getUsersBooksLiveDataMerger().observe(this, new Observer<MainViewModel.MyResult>() {
#Override
public void onChanged(#Nullable MainViewModel.MyResult myResult) {
if (myResult == null || !myResult.isComplete()) {
// Ignore, this means only one of the queries has fininshed
Log.d(TAG, "OBSERVE BLAH!!!!");
return;
}
// If you get to here, you know all the queries are ready!
// ...
List<Book> books;
List<User> users;
books = myResult.getBooksSnapshot().toObjects(Book.class);
users = myResult.getUsersSnapshot().toObjects(User.class);
Log.d(TAG, "OBSERVE MERGE users: " + users);
Log.d(TAG, "OBSERVE MERGE books: " + books);
}
});
Please note: I did do a null check in the mediatorLiveData, just took it out for testing purposes.
Somehow I need to trigger my books query if just my users is triggered AND I need to trigger my users query if just my books is triggered...I feel like there is a step before the MediatorLiveData that needs to happen so we can make sure one liveData triggers the other query. Does that make sense?
You can greatly simplify the usage by using my LiveDataZipExtensions https://gist.github.com/Benjiko99/d2e5406aab0a4a775ea747956ae16624
With them, you don't have to create an object to hold your combined result.
Example usage
val firstNameLD = MutableLiveData<String>().apply { value = "John" }
val lastNameLD = MutableLiveData<String>().apply { value = "Smith" }
// The map function will get called once all zipped LiveData are present
val fullNameLD = zip(firstNameLD, lastNameLD).map { (firstName, lastName) ->
"$firstName $lastName"
}

rxjava with for each loop

I am trying to get the data from local db by each content asyncrounous,but the issue is that I want to get the data by the order that I retrieve,at first the data for the first conent,than the second etc.,currently I am getting the data in wrong order every time I run the code ,how can I achieve this?
for (Content content : contents) {
scoreCardDisposable = AppManagers.getContentManager()
.getScoreCardsAndUpdate(content.getId())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(scoreCards -> {
...
});
compositeDisposable.add(scoreCardDisposable);
}
To make an observable stream from a List<> you can use Observable.fromIterable operator:
compositeDisposable.add(Observable.fromIterable(contents)
.flatMap(content -> {
return AppManagers.getContentManager()
.getScoreCardsAndUpdate(content.getId());
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(scoreCards -> {
...
}));
UPD:
You can use a zip operator to combine content with the getScoreCardsAndUpdate result in custom object:
class Result {
private String content;
private String result;
public Result(String content, String result) {
this.content = content;
this.result = result;
}
public String getContent() {
return content;
}
public String getResult() {
return result;
}
}
public Observable<String> getScoreCardsAndUpdate(String content) {
return Observable.just("result = " + content);
}
#Test
public void test() {
List<String> contents = Arrays.asList("1", "2", "3", "4");
Observable.fromIterable(contents)
.flatMap(content -> {
return Observable.zip(Observable.just(content),
getScoreCardsAndUpdate(content), Result::new);
})
.subscribe(scoreCards -> {
System.out.println("content = " + scoreCards.getContent() +
", " + scoreCards.getResult());
});
}
You will have to process all the items in a single thread. You can achieve this by adding a scheduler in rxjava2
.subscribeOn(Schedulers.single())
Your code should be
for (Content content : contents) {
scoreCardDisposable = AppManagers.getContentManager()
.getScoreCardsAndUpdate(content.getId())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.single())
.subscribe(scoreCards -> {
...
});
compositeDisposable.add(scoreCardDisposable);
}

RxJava2 How to share an observable with two other observer, and merge it back to a subscriber

I have the following methods
Document createDocument(String url);
List<MediaContent> getVideo(Document doc);
List<MediaContent> getImages(Document doc);
List< MediaContent> will be consumed by
void appendToRv(List<MediaContent> media);
I like to use RxJava2 such that
CreateDocument -> getVideo ->
-> appendToRv
-> getImages ->
(also, the video output should be ordered before images).
How would I go about doing that? I tried flatMap, but it seems to only allow a single method to be used
Single<List<MediaContent>> single =
Single.fromCallable(() -> createDocument(url))
// . ?? ..
// this is the part i am lost with
// how do i feed document to -> getVideo() and getImage()
// and then merge them back into the subscriber
//
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
single.subscribe(parseImageSubscription);
The DisposableSingleObserver
parseImageSubscription = new DisposableSingleObserver<List<MediaContent>>() {
#Override
public void onSuccess(List<MediaContent> media) {
if(media!=null) {
appendToRv(media);
}
}
#Override
public void onError(Throwable error) {
doSnackBar("error loading: '" + q + "'");
}
};
the single observables for getVideos and getImages
Single<List<MediaContent>> SingleGetImage(Document document ) {
return Single.create(e -> {
List<MediaContent> result = getImage(document);
if (result != null) {
e.onSuccess(result);
}else {
e.onError(new Exception("No images found"));
}
});
}
Single<List<MediaContent>> singleGetVideo(Document document ) {
return Single.create(e -> {
List<MediaContent> result = getVideo( document);
if (result != null) {
e.onSuccess(result);
}else {
e.onError(new Exception("No videos found"));
}
});
}
assuming you want to execute in parallel the getVideos and getImages requests, you can use flatMap() with zip(), zip will collect the 2 emissions from both Singles, and you can combine the 2 results to a new value, meaning you can sort the videos MediaContent list , and combine it with the images MediaContent list, and return unified list (or whatever other object you'd like):
Single<List<MediaContent>> single =
Single.fromCallable(() -> createDocument(url))
.flatMap(document -> Single.zip(singleGetVideo(document), SingleGetImage(document),
(videoMediaContents, imageMediaContents) -> //here you'll have the 2 results
//you can sort combine etc. and return unified object
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
single.subscribe(parseImageSubscription)
Observable.zip() could implement it perfect. The Observer will receive a merged result by this method.
public void zip() {
Observable<Integer> observable1 = Observable.just(1);
Observable<Integer> observable2 = Observable.just(2);
Observable.zip(observable1, observable2, new Func2<Integer, Integer, Integer>() {
#Override
public Integer call(Integer integer, Integer integer2) {
return integer + integer2;
}
}).subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Integer o) {
Logger.i(o.toString());
//Here will print 3.
}
});
}

How to emit items from a list with delay in RxJava?

I'm using Retrofit to get bookmarks from REST API:
public interface BookmarkService {
#GET("/bookmarks")
Observable<List<Bookmark>> bookmarks();
}
Now I would like to emit each item from this list with delay.
I did something similar to this in Java, but onCompleted is never triggered.
private Observable<Bookmark> getBookmarks() {
return getBookmarkService().bookmarks()
.flatMap(new Func1<List<Bookmark>, Observable<Bookmark>>() {
#Override
public Observable<Bookmark> call(List<Bookmark> bookmarks) {
Observable<Bookmark> resultObservable = Observable.never();
for (int i = 0; i < bookmarks.size(); i++) {
List<Bookmark> chunk = bookmarks.subList(i, (i + 1));
resultObservable = resultObservable.mergeWith(Observable.from(chunk).delay(1000 * i, TimeUnit.MILLISECONDS));
}
return resultObservable;
}
})
.observeOn(AndroidSchedulers.mainThread());
}
What I'm doing wrong?
Usage:
mSwipeRefreshLayout.setRefreshing(true);
getBookmarks()
.subscribe(new Observer<Bookmark>() {
#Override
public void onCompleted() {
Timber.i("Completed");
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onError(Throwable e) {
Timber.i("Error: %s", e.toString());
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onNext(Bookmark bookmark) {
Timber.i("Bookmark: %s", bookmark.toString());
mBookmarksAdapter.addItem(bookmark);
}
});
As you use a merge operation, onCompleted will be call if all Observables are completed. but Observable.never() will never complete. Use Observable.empty() instead.
According to your code, your want to emit sublist with delay. The sublist contains only one element
What you can do : flatmap your list, to emit each items. Buffer it to build a list from items, then use a delay.
private Observable<Bookmark> getBookmarks() {
return getBookmarkService().bookmarks()
.flatMap((bookmarks) -> Observable.from(bookmarks)
.buffer(1)
.scan(new Pair(0, null), (ac, value) -> new Pair(acu.index + 1, value)
.flatMap(pair -> Observable.just(pair.value).delay(pair.index, SECONDS))
.observeOn(AndroidSchedulers.mainThread());
}
it might work (not tested)

Categories

Resources