I am using Bolts framework in my Android project. I read the documents several times, but I am still confused about the difference between continueWith() and onSuccess(), because the callback method and the return value are all the same. For example,
Task task = ParseGeoPoint.getCurrentLocationInBackground(10*1000);
And what's the difference between these two methods?
task.onSuccess(new Continuation<ParseGeoPoint, Object>() {
#Override
public Object then(Task<ParseGeoPoint> task) throws Exception {
Log.d(TAG, "task done");
return null;
}
});
task.continueWith(new Continuation<ParseGeoPoint, Object>() {
#Override
public Object then(Task<ParseGeoPoint> task) throws Exception {
Log.d(TAG, "task done");
return null;
}
});
Basically, onSuccess() is just called, as its name indicates, when the call completes without errors. On the other hand, continueWith() is called always, even in case of failure. Therefore, use onSuccess() when you are interested only in retrieve the result on successful requests, and use continueWith() if you want also be able to handle failed requests.
Related
I want to implement a logic using RxJava in my android application, which requires three parallel api calls. Only the third api call has a retry logic. If, after having three attempts, the success is achieved then a subsequent call will be made for the fourth api, else only the result of first and second api calls will be passed on to the subscriber.
I tried to achieve this using Zip operator but then got stuck with retry logic for third api call.
Observable<String> observable1 = Observable.just("A","B");
Observable<Integer> observable2 = Observable.just(1,2);
Observable<Boolean> observable3 = Observable.just(Boolean.TRUE, Boolean.FALSE);
Observable.zip(observable1, observable2, observable3, new Function3() {
#Override
public Object apply(String s, Integer integer, Boolean aBoolean) throws Exception {
if (aBoolean==null){
alphabets3.retry(3).doOnComplete(new Action() {
#Override
public void run() throws Exception {
// the result will never be used
}
});
}
return s+integer+aBoolean;
}
}).subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Object o) {
Log.e("onNext-->", o.toString());
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
if any Observable failed in the Zip operator, Zip will fail the stream, the only way I know to achieve parallel execution and error handling with Zip, is to add onErrorResumeNext to each Observable, that map the error to a new model to deal with later .. and handling what you want to do in the zip mapping function ... for example
Obsevable.zip(
observable1.onErrorResumeNext{Observable.just(Model(it)},
observable2.onErrorResumeNext{Observable.just(Model(it)},
observable3.retryWhen {t is TimeOutException} //here you can add your retry logic
.onErrorResumeNext(t -> Observable.just(Model(t)),(m1 , m2, m3) -> Result())
When using the following pattern to synchronously get data from Firebase Realtime Database:
String s = Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
FirebaseDatabase.getInstance().getReference("path").orderByChild("child").equalTo("xyz").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
e.onSuccess("Got it");
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(databaseError.toException());
}
});
}
}).blockingGet();
It will hang and create an ANR error. If I use the same Firebase "innards" outside of the Single, it fires just fine. The Single without the Firebase code inside also will fire, so it seems there is some incompatibility between the two.
Any ideas?
Firebase delivers events on ui thread, waiting for result with blockingGet deadlocks it. In my opinion you should rethink app logic and subscribe without blocking with subscribe(SingleObserver)
Since you are creating your own Single, You should use DisposableSingleObserver in subscribeWith. Secondly, you shouldn't be calling blockingGet() like that. The reason is by default the Single or any observable/Processor/Flowable you create will be subscribed (run its operations on main thread) and observe on main thread. BlockingGet() causes the mainThread to pause. It's like executing Thread.sleep() on Main Thread. This always ends in a disaster.
The best option for you would be to rethink the logic you are trying to put in to the code. Since the Firebase operations are Async by nature, you should adapt your code to async pattern.
Anyways you can do something like the following to achieve what seems likes you might be trying to do. Note that I wrote the following code here so it might have syntactical errors.
Single.create(new SingleOnSubscribe<String>() {
// your firebase code
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
FirebaseDatabase.getInstance().getReference("path").orderByChild("child").equalTo("xyz").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
e.onSuccess("My String");
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(databaseError.toException());
}
});
}
})
.subscribeOn(Schedular.io())
.observeOn(AndroidThread.mainThread()) // if you aren't doing intensive/long running tasks on the data you got from firebase
.subscribeWith(new DisposableSingleObserver<String>() {
public void onSuccess(String myString) {
mMyString = myString;
}
public void onError(Throwable t) {
Timber.e("error in fetching data from firebase: %s", t);
}
});
I'm totally new to Firebase and need to know how to check if my writing task was successful because if I don't, the MainActivity starts and messes up my Register progress.
This checks if Username is already taken and registers the user if it isn't:
Query usernamequery = myRef.orderByChild("Username").equalTo(Username);
usernamequery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
// TODO: handle the case where the data already exists
editText.setError("Username taken!");
return;
}
else {
// TODO: handle the case where the data does not yet exist
myRef.child("Users").child(Username).child("Userid").setValue(user.getUid());
myRef.child("Users").child(Username).child("Username").setValue(Username);
startActivity(maps);
finish();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(Username.this, "Error", Toast.LENGTH_LONG).show();
}
});
But I want the Intent to Main Activity (maps) only be fired when
myRef.child("Users").child(Username).child("Userid").setValue(user.getUid());
and the other one is finished with its task and is successful.
What can I do?
To know when a write operation has completed on the server, add a completion listener:
myRef.child("Users").child(Username).child("Userid").setValue(user.getUid(), new DatabaseReference.CompletionListener() {
void onComplete(DatabaseError error, DatabaseReference ref) {
System.err.println("Value was set. Error = "+error);
// Or: throw error.toException();
}
});
If there was an error, details will be in the error operation. If the write operation was completed without problems, the error will be null.
If you want to write to multiple locations with a single operation, you'll want to look at the update() method
The correct overload for setValue() is documented here.
I've been wondering what's the best way to tackle the issue of token refresh.
I'm connecting to an API which supplies me with a auth-token, if sometime time during the calls i get a INVALID_AUTH i need to re-authenticate.
So for the naive implementation i did this
#SuppressWarnings("unchecked")
#Override
public Observable<User> getUsers() {
return runCommandAndrefreshAuthIfNecessary(new RequestCommand() {
#Override
public Observable create() {
return createService(UsersApi.class).getUsers();
}
});
}
private Observable runCommandAndrefreshAuthIfNecessary(final RequestCommand command) {
return command.create()
.onErrorResumeNext(new Func1<Throwable, Observable<?>>() {
#Override
public Observable<?> call(Throwable throwable) {
return handleRefreshToken(command);
}
});
}
private Observable<?> handleRefreshToken(final RequestCommand command) {
return refreshToken().flatMap(new Func1<Boolean, Observable<?>>() {
#Override
public Observable<?> call(Boolean aBoolean) {
return command.create();
}
});
}
As you can see i'm just wrapping the retrofit command, if i get an error i run refreshToken(), the token refreshes and i run the retrofit command again, so finally the Observable is passed back to the subscriber. Works as expected.
The thing i'm struggling with, is what happens i a multiple calls are made, for example, i'm calling getUsers and getFlags one after another. both of them get the INVALID_AUTH, currently both of the fire refreshToken(), which is bad.
i'm looking for a rx-java way to manage the calls, meaning after the first call of getUsers fires refreshToken, any call after that needs to wait for the refreshToken to end, only then fire the retrofit command.
Any suggestion will be appreciated.
You can use .cache() on the Observable for the token refreshing:
http://reactivex.io/documentation/operators/replay.html
I have Task, that fetches results from server. While storing that data to database in bolt's .continueWith It got interrupted with something "completedImmediately" (found while debugging cycle).
this.someMethod.getStatements()
.continueWith(new Continuation<List<Statement>, Object>() {
#Override public Object then(Task<List<Statement>> task) {
for (Statement statement : task.getResult()) {
saveStatement(statement);
}
return null;
}
});
Found solution, there was a problem in inner method, which was storing data to database.