I am struggling with correct exception handling in RxJava2. I am trying to refresh my token inside an OkHttp3 interceptor like this:
tokenRepository.refreshToken(authStateManager.current.refreshToken!!)
.doOnError {
Log.w(TAG, "Could not obtain new access token.")
authStateManager.signOut()
}
.subscribe { tokenResponse ->
Log.d(TAG, "Obtained new access token: " + tokenResponse.toString())
authStateManager.updateAfterTokenResponse(TokenResponse.jsonDeserialize(tokenResponse.toString()), null)
token = authStateManager.current.accessToken
}
It works nicely if the refresh token is valid and the request returns 200. But if the refresh token is invalidated and I get some error code (for example 400), the doOnError block is executed but it then proceeds to the subscribe block where the following exception is thrown:
retrofit2.adapter.rxjava2.HttpException: HTTP 400
at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onNext(BodyObservable.java:54)
at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onNext(BodyObservable.java:37)
...
I tried using onErrorReturn and onErrorResumeNext but I would like to skip the subscribe block completely (it there is an error, just log user out and don't try to do anything). Is it possible? The refreshToken method returns Single<JsonObject> type of response.
Can't you use the onError in the subscription? I like using RxKotlin for this. For example, I can do the following:
.subscribeBy(onSuccess = {
// do Something in on success
}, onError = {
// do something in onError
})
So in your case, it could be something like:
tokenRepository.refreshToken(authStateManager.current.refreshToken!!)
.subscribeBy(onSuccess = {
Log.d(TAG, "Obtained new access token: " + it.toString())
authStateManager.updateAfterTokenResponse(TokenResponse.jsonDeserialize(it.toString()), null)
token = authStateManager.current.accessToken
}, onError = {
Log.w(TAG, "Could not obtain new access token.")
// the variable "it" here is a throwable which means you can determine if it's a RetrofitException and what status code is returned
})
However, if you do not want to use RxKotlin, you can handle your Rx subscriptions like this in Kotlin:
.subscribe({ Log.d(TAG, "Obtained new access token: " + it.toString()) },
{ Log.e(TAG, "Error is ${it.message}") })
You can handle the error in the second part of the function where the Log.e is being used.
I just had the same problem but I fixed it now. I don't know if my explanation is correct but it seems like doOnError doesn't handle the exception.
You need to use the subscribe() method that takes an onError Consumer as a parameter and handle the error there:
subscribe(Consumer<? super T> onSuccess, Consumer<? super Throwable> onError)
In Java it would look like this:
.subscribe(tokenResponse -> {
Log.d(TAG, "Obtained new access token: " + tokenResponse.toString());
authStateManager.updateAfterTokenResponse(TokenResponse.jsonDeserialize(tokenResponse.toString()), null);
token = authStateManager.current.accessToken;
}, throwable -> {
Log.w(TAG, "Could not obtain new access token.");
authStateManager.signOut();
}));
Related
I trying to get reCaptcha-code for send to Firebase server. Read here, implement Executor to Activity and write this code at onClick:
override fun onClick(view: View?) {
SafetyNet.getClient(this).verifyWithRecaptcha("Here i write my API Key")
.addOnSuccessListener(this as Executor, OnSuccessListener { response ->
val userResponseToken = response.tokenResult
Log.d("TAG", userResponseToken.toString())
})
.addOnFailureListener(this as Executor, OnFailureListener { e ->
if (e is ApiException) {
Log.d("TAG", "Error: ${CommonStatusCodes.getStatusCodeString(e.statusCode)}")
} else {
Log.d("TAG", "Error: ${e.message}")
})
Logs are not printing when I click button and call onClick method, but this method works. Tell me please, what I do wrong?
I have tested this and it works without using the as Executor like the below example:
SafetyNet.getClient(this)
.verifyWithRecaptcha("MY API KEY")
.addOnSuccessListener(this, OnSuccessListener { response ->
// Indicates communication with reCAPTCHA service was successful.
val userResponseToken = response.tokenResult
if (response.tokenResult?.isNotEmpty() == true) {
// Validate the user response token using the
// reCAPTCHA siteverify API.
}
})
.addOnFailureListener(this, OnFailureListener { e ->
if (e is ApiException) {
// An error occurred when communicating with the
// reCAPTCHA service. Refer to the status code to
// handle the error appropriately.
Log.d("TAG", "Error: ${CommonStatusCodes.getStatusCodeString(e.statusCode)}")
} else {
// A different, unknown type of error occurred.
Log.d("TAG", "Error: ${e.message}")
}
})
I have this code
val collectionDb = db.collection(collection)
.add(user4)
.addOnSuccessListener { documentReference ->
Log.d("SUCCESS", "DocumentSnapshot added with ID: ${documentReference.id}")
println(documentReference.id)
}
.addOnFailureListener { e ->
Log.w("FAIL", "Error adding document", e)
}
//here wait
println("more code")
}
The output is first more code and later the DocumentReference.id
But I want that on //here wait the script pause until there is a success or a fail.
I'm new so it would be very nice when you can help me out.
When you want to execute script after success or failure just create a method and call it from success or failure callback as below.Please check if below is useful.
val collectionDb = db.collection(collection)
.add(user4)
.addOnSuccessListener { documentReference ->
Log.d("SUCCESS", "DocumentSnapshot added with ID: ${documentReference.id}")
println(documentReference.id)
doRequiredOperation("Success")
}
.addOnFailureListener { e ->
Log.w("FAIL", "Error adding document", e)
doRequiredOperation("failuire")
}
fun doRequiredOperation(msg:String){
if(msg.equals("Success"){
println("more code"). //It will execute after success callback
}
}
But I want that on //here wait the script pause until there is a
success or a fail.
That would be impossible. That's the point of an asynchronous operation - it will complete, whether it succeeds or fails, at a later point in time and when that time will be, we can't really be sure. I have a Q&A here if you want more information on how to handle these types of functions:
Why does my function that calls an API return an empty or null value?
but your best bet would be to add all the relevant code inside your addOnSuccessListener listener, because this is where you know that the request has completed and it succeeded
I am using Retrofit and RxJava to make network requests like this:
How I am declaring request:
#POST("auth/profile/edit/")
fun updateProfile(#Body body: ProfileUpdateBody): Single<Response<Void>>
How I am calling:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnSuccess {
if(it.isSuccessful)
Log.d("----------", "Success!")
else
Log.d("----------", "Not Successfull!")
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({
}, {
})
Some code have been dropped for readability. The probem is even though I get responses back with 401 or 400 statuses, doOnSuccess is being called. Should not the doOnError be called here? I am confused.
As a result my logact is showing "Not Successful" message. How can make sure that doOnErro is called when I get responses back with 401 or 400 statuses?
Or can I parse the incoming response to Throwable and call doOnError() function?
Change the Retrofit API call to return Completable:
#POST("auth/profile/edit/")
fun updateProfile(#Body body: ProfileUpdateBody): Completable
then handle the "success case" via doOnComplete:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnComplete {
Log.d("----------", "Success!")
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({ }, { })
The real question is, why would you want to throw and exception when the request fails?
The correct processes are being followed here, doOnSuccess is being called as intended because the request has returned a response without encountering an exception being thrown. Regardless of whether the request's response is successful or not.
You should handle the state of your response accordingly and not throw arbitrary exceptions for it:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
if (response.isSuccessful()) {
// handle success
} else {
// handle failure
}
}, t -> {
// handle thrown error
yourErrorHandlerMethod(t);
})
The response you getting is correct, the response is shown in doOnSuccess cuz the API you hitting got successfully hit, no matter what was the response code.
doOnError is called when actual API call is failed like network drop in the middle or some server-side issues.
Or can I parse the incoming response to Throwable and call doOnError() function?
You cant do this instead, you can handle the response in doOnSuccess as
try {
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnSuccess {
if(it.isSuccessful) // responce code = 200/201
Log.d("----------", "Success!")
else if (it.responseCode == 400 ){
Log.d("----------", "Not Found!")
// Call a method that handles this according to your requirement.
PageNotFoundHandler();
// OPTIONAL throw new UserException();
}
else if (it.responseCode == 401 ){
Log.d("----------", "Not Authorised!")
// Call a method that handles this according to your requirement.
TokenExpiredHandler(); //OR
UnAuthorizedAccessHandler();
// OPTIONAL throw new UserException();
}
else {
Log.d("----------", "Some another Error!")
// Call a method that handles this according to your requirement.
// OPTIONAL throw new UserException();
}
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({
}, {
})
} catch
{
ErrorHandler();
}
Or can I parse the incoming response to Throwable and call doOnError() function?
As you mention that you want a throwable, you can achieve it by using the try-catch block.
Just throw a custom EXCEPTION, you have to create a new Custom Exception class for it.
I'm introducing myself about RxJava2, but i feel like i'm doing something wrong. In my case, i want to do some following asynchronous actions.
In this example, the first action is to check if the device is connected (wifi or data, let's admit it take time), then i want to connect to an api and then i want to do a http call for get a list (observable) and then work with it. If one of those operation fail, an onError or exception should be raised and handled in the subscribe.
I have this code who works:
Single.create((SingleEmitter<Boolean> e) -> e.onSuccess(Connectivity.isDeviceConnected(MainActivity.this)) )
.subscribeOn(Schedulers.io())
.flatMap(isDeviceConnected -> {
Log.i("LOG", "isDeviceConnected : "+ isDeviceConnected);
if(!isDeviceConnected)
throw new Exception("whatever"); // TODO : Chercher vrai erreur
return awRepository.getFluxAuthenticate(host, port, user, password); // Single<DisfeApiAirWatch>
})
.toObservable()
.flatMap(awRepository::getFluxManagedApps) // List of apps : Observable<AirwatchApp>
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
But do a single who emmit a boolean for a simple "if" sounds wrong. A Completable seems more logical (work or not, continue or stop). I tried with the following code but it's not working.
Completable.create((CompletableEmitter e) -> {
if(Connectivity.isDeviceConnected(MainActivity.this))
e.onComplete(); // Guess not good, should call the complete of subscribe ?
else
e.onError(new Exception("whatever"));
} ).toObservable()
.subscribeOn(Schedulers.io())
.flatMap(awRepository.getFluxAuthenticate(host, port, user, password)) //Single<DisfeApiAirWatch>
.toObservable()
.flatMap(awRepository::getFluxManagedApps) // List of apps : Observable<AirwatchApp>
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
How to make this code work ?
I know i can do a first subscribe on the complatable and in the "onSuccess" of this one write another flux / the rest of the code. But i don't think stack flows inside each other is a good solution.
Best regards
Completable has no value so flatMap will never be invoked. You have to use andThen and make the authentication success value the input for the subsequent flatMap:
Completable.create((CompletableEmitter e) -> {
if(Connectivity.isDeviceConnected(MainActivity.this))
e.onComplete();
else
e.onError(new Exception("whatever"));
})
.subscribeOn(Schedulers.io())
.andThen(awRepository.getFluxAuthenticate(host, port, user, password)) // <-----------
.flatMapObservable(awRepository::getFluxManagedApps)
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
I'm trying to recover from errors using RxJava and GRPC. This is my observable:
Observable<Object> observable = Observable.fromCallable(() -> {
try {
Grpc.MyRequest request = Grpc.MyRequest.newBuilder()
.setToken(mToken)
.build();
Grpc.MyResponse reply = mStub.mytest(request);
return reply;
} catch (Exception e) {
///
}
}).cache();
And this is the subscription:
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> {
})
.subscribe((result) -> {
MyResponse res = ((MyResponse) result);
if (res.getCode()!=0) {
//Check error code and try to refresh token and repeat this request after.
}
},throwable -> {
throwable.printStackTrace();
});
So, when I get the error from my GRPC service, depending on the error code, I want to try and recover from it by doing another request, and then repeating the original request. I'm not sure how to use RxJava retrywhen.
What is the most elegant way of doing something like this?
Error recovery in an observer chain does require a bit of tap dancing, and is by no means elegant. However, it can be contained in the observer chain.
boolean isRecoverable( Throwable t ) {
// this test can be as sophisticated as you want
if ( t instanceof StatusRuntimeException ) {
return true;
}
return false;
}
...
.retryWhen( throwableObservable ->
throwableObservable.flatMap( t -> isRecoverable( t )
? Observable.just("")
: Observable.error( t ) )
...
This approach allows you to decide what you want to do with the error. You could add a delay the just() so that you don't retry immediately. Instead of the just(), you could return an Observable that fetches a new API token.