I want to perform tasks in RxJava one by one.
For Example:-
1. Fetch User Ids from Server
2. Fetch Users from server by thier Ids.
I have tried this method
public Observable<List> getUids(){
return Observable.create(emitter -> {
List<String> uids = new ArrayList<>();
//fetchData from server
emitter.onNext(uids);
});
}
public Observable<User> getUser(String uid){
return Observable.create(emitter -> {
User user = new User();
//fetchData user from server
emitter.onNext(user);
});
}
//Executing this code like
getUids().flatMapIterable(ids -> ids)
.flatMap(this::getUser)
.subscribe(new Observer<User>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(User user) {
print("next "+user.getName());
}
#Override
public void onError(Throwable e) {
print("error "+e.getMessage());
}
#Override
public void onComplete() {
print("complete");
}
});
There are some problems in it
1.this is not calling Subscriber's onComplete() method when all users are fetched.
2.if there is an error in getUser method, app is crashing. with io.reactivex.exceptions.UndeliverableException exception
Can you please tell me where I am mistaking?
Call emitter.onComplete() in your getUids() and getUser(...) Observables and then append .toList() after .flatMap(this::getUser).
Returns a Single that emits a single item, a list composed of all the items emitted by the finite source ObservableSource.
UndeliverableException is a wrapper for the exception that is happening in your .flatMap(this::getUser). I can not help you more with the information that you provided, what is it that you want to happen when an exception is thrown?
Related
I am new to doing asynchronous programming in Android Java. I am wondering if there is a way to run another Callback after an initial Callback function has completed. Right now, I think they are running in parallel even though the second relies on the first.
First Callback:
// GETTING USER
private interface FirestoreUserCallback {
void onCallback (User myUser);
}
private void getUser(final FirestoreUserCallback firestoreCallback) {
Task<DocumentSnapshot> task = fStore.collection("users").document(fAuth.getCurrentUser().getUid()).get();
task.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
user = documentSnapshot.toObject(User.class);
firestoreCallback.onCallback(user);
Log.d(TAG, "user created");
}
});
task.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "user creation failed");
}
});
}
Second Callback:
// GETTING ALL DOCUMENTS
private interface FirestoreDocumentCallback {
void onCallback (List<TableEntries> myEntries);
}
private void getDocuments (final FirestoreDocumentCallback firestoreDocumentCallback) {
fStore.collection("result")
.document(Integer.toString(user.getCompanyNumber())) // need to use User object returned from the first Callback
.collection("SAM").get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
List<TableEntries> results = new ArrayList<>();
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
// add objects to results ArrayList ...
Log.d(TAG, document.getId() + " => " + document.getData());
}
firestoreDocumentCallback.onCallback(results);
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
onCreate:
getUser(new FirestoreUserCallback () {
#Override
public void onCallback(User myUser) {
user = myUser;
}
});
getDocuments(new FirestoreDocumentCallback() {
#Override
public void onCallback(List<TableEntries> myEntries) {
entries = myEntries;
}
});
getDocuments() relies on the user variable being given its value from the first Callback. I'm receiving this error:
java.lang.NullPointerException: Attempt to invoke virtual method 'double java.lang.Double.doubleValue()' on a null object reference
Callbacks are looking fine. You just need to check if your value is null or not before accessing it. Just add a null check
if(doubleValue!=null)
Using RxJava. First, we fetch the user and then fetch the documents. Rx-Java has an operator flatmap. flatmap is used to execute the sequential tasks, where the second task is dependent on the data from the first task.
final CompositeDisposable disposable = new CompositeDisposable();
//function to fetch user data
Single<User> getUser(){
return API.getUserData(...);
}
//function to fetch ducuments
Sinlge<UserDetail> getDocuments(int userId){
return API.getUserDetail(userId, ...);
}
//Subscribe
disposable.add(getUser()
.flatmap(user-> return getDocuments(...))
.subscribeOn(Scheduler.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObservable(){
#Override
public void onSuccess(UserDetail userDetail){
Log.v("Api result", "Successful";
//Do some work
}
#Override
public void onError(Throwable e)
Log.v("Api result", "Error Returned");
}
}));
If either of the API call fails, onError() is called. If first API fails, second API call is not executed and onError() is called.
The simplest solution for your use-case is to pass both queries to Tasks.whenAllSuccess() method, as explained in my answer from the following post:
Firestore - Merging two queries locally
So once the task is complete, you can use the elements from both queries. Another solution might be to use Android Jetpack with LiveData along with ViewModel, as the Android team recommends.
This question already has answers here:
Combining Firebase realtime data listener with RxJava
(5 answers)
Closed 2 years ago.
Does anyone knows how to connect Firebase with RxJava so when I load ALL my data from database then it runs arrayAdapter.notifyDataSetChanged() ??
I was thinking to write it in onComplete() method but it still runs before loading all data
Completable.fromCallable(new Callable<List<cards>>() {
#Override
public List<cards> call() throws Exception {
newUserDb.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.child(currentUID).child("sex").exists()) {
myInfo.put("sex", dataSnapshot.child(currentUID).child("sex").getValue().toString());
}
if (dataSnapshot.child(currentUID).child("dateOfBirth").exists()) {
int myAge = stringDateToAge(dataSnapshot.child(currentUID).child("dateOfBirth").getValue().toString());
myInfo.put("age", String.valueOf(myAge));
}
if (dataSnapshot.child(currentUID).child("connections").child("yes").exists()) {
for (DataSnapshot ds : dataSnapshot.child(currentUID).child("connections").child("yes").getChildren()) {
if (!dataSnapshot.child(currentUID).child("connections").child("matches").hasChild(ds.getKey())) {
Log.d("rxJava", "onDataChange: " + ds.getKey());
first.add(ds.getKey());
getTagsPreferencesUsers(dataSnapshot.child(ds.getKey()), true);
}
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
return rowItems;
}
}).subscribeOn(Schedulers.computation())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
Log.d("rxJava", "Test RxJAVA, onSubscribe");
}
#Override
public void onComplete() {
Log.d("rxJava", "Test RxJAVA, onComplete");
}
#Override
public void onError(Throwable error) {
Log.d("rxJava", "Test RxJAVA, onError");
}
});
and the output is
2020-06-04 23:36:28.797 29515-29515/com.example.tinderapp D/rxJava: Test RxJAVA, onSubscribe
2020-06-04 23:36:28.800 29515-29612/com.example.tinderapp D/rxJava: Test RxJAVA, onComplete
2020-06-04 23:36:29.018 29515-29515/com.example.tinderapp D/rxJava: onDataChange: a4hqGgAJBRTVJOlPp3blNDt5v7q1
2020-06-04 23:36:29.022 29515-29515/com.example.tinderapp D/rxJava: onDataChange: aA9HAOtaB7ao6vzKqqBNp0iaBev2
I would say, this is expected behavior, which is described in the following:
Completable.fromCallable
fromCallable takes a Lambda, which returns a List on subscription. In your case, a database-connection is opened as-well, which is basically fall through, because the callback is registered via callback non-blocking.
subscribeOn
this makes sure, that the subscribeAcutal from fromCallable is called from given scheduler. Therefore the subscribing thread and and emitting thread are decoupled.
You get onComplete first, because the fromCallable will return rowItems immediately and the database connection will stay open, because you did not remove the listener. After a while you get data-base callback logs, because the database connection is still open and the listener is still registered.
You want to actually do something like this:
Single.create<List<Card>> { emitter ->
// register onChange callback to database
// callback will be called, when a value is available
// the Single will stay open, until emitter#onSuccess is called with a collected list.
newUserDb.addListenerForSingleValueEvent {
// do some stuff
emitter.onSuccess(listOf()) // return collected data from database here...
}
emitter.setCancellable {
// unregister addListenerForSingleValueEvent from newUserDb here
}
}.subscribeOn(Schedulers.computation())
.subscribe(
// stuff
)
If you want to have a constant stream of updates, exchange Single with Observable/ Flowable
I have this code :
getLocationObservable() // ---> async operation that fetches the location.
// Once location is found(or failed to find) it sends it to this filter :
.filter(location -> { // ---> I want to use this location in the the onNext in the end
after finishing some calculation here, I either return 'true' and continue
to the next observable which is a Retrofit server call, or simply
return 'false' and quit.
})
.flatMap(location -> getRetrofitServerCallObservable( location )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Observer<MyCustomResponse>() {
#Override
public void onSubscribe(Disposable d) {
_disposable = d;
}
#Override
public void onNext(MyCustomResponse response) {
// I want to be able to use the `location` object here
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
I want to be able to use the location object from line 3(first observable), in the "onNext" that is trigerred by the second observable.
I can't manage to work it out.. any help would be much appreciated.
Instead of
getRetrofitServerCallObservable( location )
you could map the result to be a Pair (from your favourite library) of the response and the location:
getRetrofitServerCallObservable( location ).map(response -> Pair.create(location, response))
Then, in your onNext, you'd be receiving Pair<Location,MyCustomResponse> instances.
If you don't want to use a Pair class, you could use Object[], but if you do, please don't tell me about it :P
I'm new to RxJava. I would like to download some data for each TempoAccount entity from given collection and store it all in a map accountsWithProjects. When the code of last onNext(TempoAccount tempoAccount) is completed I'd like to call filterAccountsWithProjects(accountsWithProjects) method. Is there some simple way to achieve it?
private void getProjectsForEachTempoAccount(Collection<TempoAccount> tempoAccounts) {
final Map<TempoAccount, Collection<TempoProject>> accountsWithProjects =
new HashMap<>(tempoAccounts.size());
Observable<TempoAccount> accountsObservable = Observable.from(tempoAccounts);
accountsObservable
.compose(ObservableUtils.applySchedulers())
.subscribe(new ObserverAdapter<TempoAccount>() {
#Override
public void onError(Throwable e) {
view.notifyAboutError(e.getMessage());
}
#Override
public void onNext(TempoAccount tempoAccount) {
jira.requestProjectsInfoForTempoAccount(String.valueOf(tempoAccount.getId()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new ObserverAdapter<Collection<TempoProject>>() {
#Override
public void onError(Throwable e) {
view.notifyAboutError(e.getMessage());
}
#Override
public void onNext(Collection<TempoProject> projects) {
accountsWithProjects.put(tempoAccount, projects);
}
});
}
#Override
public void onCompleted() {
filterAccountsWithProjects(accountsWithProjects);
}
});
}
Problem: In the code above filterAccountsWithProjects(accountsWithProjects) is fired before all observables from onNext(TempoAccount tempoAccount) are completed.
Edit:
I want to create an Observable of such a type: Observable<Map<TempoAccount, Collection<TempoProject>>.
I have two observables given:
Observable<TempoAccount> accountsObservable = Observable.from(tempoAccounts)
Observable<Collection<TempoProject>> projectsForAccountObservable = jira.requestProjectsInfoForTempoAccount(TempoAccount account)
So my questions is: can I connnect them somehow and create the map having these two observables.
You should use the flatMap() function on your original stream to do the things you are currently doing in the onNext(). Also, you don't need to filter the stream in onComplete(). You could use filter() on the stream itself and deal with the problem in a more "Reactive" way.
Here is an example:
accountsObservable
.compose(ObservableUtils.applySchedulers())
.map(tempoAccount -> new Pair<TempoAccount, Collection<TempoProject>>(tempoAccount, fetchInfoAccountForTempoAccount(tempoAccount)))
.filter(pair -> hasProjects(pair))
.toMap(pair -> pair.first(), pair -> pair.second)
.subscribe(...)
EDIT:
Updated the suggested answer - you get the TempoAccounts, then you map each account to a Pair of account and collection of TempoProjects. You filter the pairs to see if you have any projects and then you use toMap() to create your desired result. Be aware, that to get toMap() working, your observables have to call onComplete() when the end of the stream is reached.
I'm new in rxjava or rxandroid, and looking for a better way dealing with multiple requests. I need to get the token from server and use the result as a parameter to do login verification and if it returns success then get the sessionId through getSessionId method.
I've considered about zip or merge, but I don't think it'll work. So can you give me an idea or I don know , train of thought?
Thank you.
Here's my code:
private void getToken(final String name , final String pwd){
api.newToken()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<TokenModel>() {
#Override public void call(TokenModel tokenModel) {
String token = tokenModel.request_token;
if (!"".equals(token)){
login(token, name, pwd);
}else {
Timber.e("got token failed");
}
}
});
}
private void login(String token, String name, String pwd){
api.validateToken(token, name, pwd)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<TokenModel>() {
#Override public void call(TokenModel tokenModel) {
String token = tokenModel.request_token;
if (!"".equals(token)){
getSessionId(token);
}else {
Timber.e("got token failed");
}
}
});
}
private void getSessionId(String token){
api.newSessionn(token)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<TokenModel>() {
#Override public void onCompleted() {
//go to home activity
}
#Override public void onError(Throwable e) {
//handle error
}
#Override public void onNext(TokenModel tokenModel) {
//store session id
}
});
}
Your first subscription call your second subscription, ...
You can avoid this using flapmap operator.
api.newToken(...)
.flapMap(token -> api.validateToken(token))
.flapMap(token -> api.newSession(token)).subscribe()
New observable in a subscription can offen be replaced by a flatMap call.
If you want to manage your error, in a flatMap, if the token is invalid, your can return an error observable instead of returning new api call observable.
.flatMap(token -> if(token.isValid){ return api.newCall(); } else { return Observable.error(...); ;)