Basically I have to first login the user, if its successful them I have to add a shop and logout. The retrofit interface is given below
#POST("merchant/register")
Observable<BaseResponse<String>> Login(#Body Merchant merchant);
#PUT("merchant/{username}")
Observable<BaseResponse<Merchant>> Logout();
#POST("shop")
Observable<BaseResponse<Shop>> addShop(#Body Shop shop);
The observables are created as given
Observable<BaseResponse<String>> loginObs = apiService.Login(merchant);
Observable<BaseResponse<Merchant>> addShopObs = apiService.addShop(shop);
Observable<BaseResponse<String>> logoutObs = apiService.Logout();
The Base response has a success field based which i should decide if the login was successful. I think i can use map to verify the success of first login observer, but i don't know what to do if the login fails. How can i cancel the whole chain?
You can begin with the loginObs, flatMap the loginResponse to another observable depending on the success of the login, so either return the addShopObs or return an error observable
(that will terminate the chain with an error)
Then you can continue normally to flatMap the merchantResponse to the logoutObs.
Here's how it can be achieved:
loginObs(merchant)
.flatMap(loginResponse -> {
if (/*successful check*/)
return addShopObs;
else
return Observable.error(new Exception("Login failed!"));
// or throw your own exception, this will terminate the chain and call onError on the subscriber.
})
.flatMap(merchantResponse -> logoutObs)
.subscribe(logoutResponse -> {
/*all operations were successfull*/
}, throwable -> {
/*an error occurred and the chain is terminated.*/
});
Related
How to continue executing a Retrofit network call observable that takes in as items as input obtained from another observable that is converted to flatMapIterable even if an error was encountered and stop iterating only if I encounter a specific HTTP status code?
I have a list of JSON requests saved in shared preferences, that I need to send one by one using Retrofit, only stopping whenever I get a certain HTTP status code. If I get other exceptions, I just need to continue sending the next items in the requests list. Whenever a request receives a successful response, I remove that particular request from my request list. If other requests have encountered errors, they do not get removed from the list and I save them again to the shared preferences.
So far, I do this inside a ViewModel object. First, I fetch these requests via a method (paramRepository.getSavedOfflineRequest()) that returns an RxJava Observable<List<Request>>. I want to iterate through all the requests so that I can send the items as inputs to apiService.sale, which is my Retrofit call, so I use flatMapIterable. If the request is successful, I remove the request and save a Transaction object to DB.
public LiveData<UploadStatus> startUploading() {
MutableLiveData<UploadStatus> uploadStatus = new MutableLiveData<>();
compositeDisposable.add(paramRepository.getSavedOfflineRequest()
.doOnComplete(() -> uploadStatus.setValue(UploadStatus.NO_ITEMS))
.flatMapIterable( requests -> requests)
.flatMapCompletable(request -> apiService.sale(saleUrl, BuildConfig.ApiKey,request)
.doOnSuccess(response -> {
requestList.remove(request);
transactions.add(createTransaction(request, response));
}).ignoreElement()
)
.andThen(saveUploadedToDb(transactions))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> uploadStatus.setValue(UploadStatus.SUCCESS),
error -> {
Log.d(TAG, "error");
if (error instanceof HttpException) {
HttpException httpException = (HttpException) error;
int statusCode = httpException.code();
if (statusCode == 401) {
Log.d(TAG, "logged out");
uploadStatus.setValue(UploadStatus.LOGGED_OUT);
}
} else {
uploadStatus.setValue(UploadStatus.FAIL);
}
}));
return uploadStatus;
}
I expect that if if I get other errors/exceptions, I just continue making calls using apiService.sale with the next Request item. But I noticed that the whole chain stops when just one error is encountered, thus the other Requests have not been sent.
I have tried onErrorResumeNext, but it expects a return of another kind of Exception, which is totally different from what I want (do nothing for other Exceptions).
You probably want the observable to return UploadStatus and map the response.
E.g.
.map { response ->
switch(response.code()) {
case 200:
return UploadStatus.SUCCESS;
case 401:
return UploadStatus.LOGGED_OUT;
default:
return UploadStatus.FAIL;
}
}
In the case of an exception, you can use onErrorReturn to return UploadStatus.FAIL/ERROR. This won't terminate the stream.
.onErrorReturn { UploadStatus.FAIL }
I have a login endpoint by which I receive an authToken and a refreshToken. The first one expires in an hour, so I should use the second one to refresh it and continue the application flow.
Now my app is filled with retrofit calls all over the place and I potentially could get a 401 at any given moment, so how can I make it so every time I get a 401 a refresh token is issued, and then the original request retried?
This is my refresh signature:
#POST("/auth/actions/refresh")
fun refreshToken(#Body tokenRefresh: TokenRefresh): Single<LoginResponse>
I thought about making a base repository class with a method withAuth() that takes any Observable/Single/Flowable and then applies this logic but I cannot find a way to implement it.
Saw a bunch of implementations but none of them match my needs... can anyone push me in the right direction?
This is the closest I've found, however there seems to be some errors on the flatmapping
I just came across similar requirement and came up with following solution. It's pretty simple, it just makes one attempt to call REST endpoint and if that call fails with HTTP 401 it reauthenticates and repeats the call again. Otherwise it just emits the original error.
fun <T> Single<T>.withAuth() = retryWhen { errors ->
var firstAttempt = true
errors.flatMapSingle { error ->
if (firstAttempt && error is HttpException && error.code() == 401) {
firstAttempt = false
reauthenticate()
} else {
Single.error(it)
}
}
}
where the reauthentication function has the following signature:
fun reauthenticate(): Single<AnyAuthResponse>
Please note that the concrete exception type might depend on HTTP implementation you actually use, so you may want to update the condition to detect HTTP 401 response, but the code should give you an overall picture of how to solve your problem.
I think you can do this without modifying all calls. Add an Authenticator to your Retrofit
Refreshing OAuth token using Retrofit without modifying all calls
You can use Interceptor to intercept each request and check whether it returns 401 - UnAuthorised Access and iff then refresh the token and replay the current API request.
public final class SessionInterceptor implements Interceptor {
// gets intercept
#Override public Response intercept(#NonNull final Chain chain) throws IOException {
final Request request = chain.request();
final Response response = chain.proceed(request);
final ResponseBody responseBody = response.body();
if (response.code() == 401) {
synchronized (this) {
// Refresh your token
// Update your authToken + Refreshed token
final retrofit2.Response response = refreshToken();
}
}
// Replay the original request
// Perform request, here original request will be executed
final Request original = chain.request();
final Request.Builder builder = original.newBuilder();
// Set your new refreshed token
if (accessToken.isSet()) {
builder.header(AUTHORIZATION, String.format(BEARER,
accessToken.get()));
}
final Request request = builder.method(original.method(), original.body()).build();
return chain.proceed(request);
}
}
I have a retrofit service that contains the following interface
public interface ApiService {
#GET("/users/me")
Observable<Account> authenticateUser(#Header("Authorization") String auth);
#GET("/membership/{userId}")
SubscriptionStatus getSubscriptionStatus(#Path("userId") String userId);
}
I would like to define a method to make an api call to get the Account which contains the userId, and then use this ID to make a second API call to get The users subscription status. SubscriptionStatus contains a boolean and if its true I would like the method to return Observable.
This is how I have gone about it so far :
public Observable<Account> doLogin(ApiService service , String credentials) {
return service.authenticateuser(base64) // gets Account observable
.doOnNext(account -> {
currentAccount = account; // setting the Account Variable
})
.flatMap(account -> service.getSubscriptionStatus(account.getUserId())) // get Account Subscription status
... //unsure where to go from here I need to check
//the subscriptionStatus object and return account
//observable if condition is valid
}
How about
return service.authenticateuser(base64) // gets Account observable
.doOnNext(account -> {
currentAccount = account; // setting the Account Variable
})
.flatMap(account ->
service.getSubscriptionStatus(account.getUserId())
.filter(status -> status.isActive()) // don't let status pass if not active
.map(status -> account) // we actually need the original account
);
In case you need both the account and the status, the last map() should return some composite type of the two:
.map(status -> new Pair<>(account, status))
My goal
I want to check if the server's token is still valid, let's say I know that information just by calling this getter : preferenceHelper.isTokenValid(). Then, if the token is invalid, calling a request to get a new token and updating the token locally, THEN, proceed with the next request to post the point to the server. That's because I need a valid token in order to make any further server request.
Let say I have those two server request that returns Observable:
This request is meant to get the server token, then upon reception, updating it.
Observable<Response<EntityToken>> updateServerToken = retrofitApi.authenticate(username,password);
This request is meant to post the current location to the server, then if it succeed, return the saved point
Observable<Response<EntityPoint>> updateServerToken = retrofitApi.postPoint(point);
Issues i'm facing currently:
Both observable that needs to be merged are from different type
Executing the token update request only if it needs to
Waiting for the token update request to complete before executing the request to post points
How should I write my RxJava Observable to satisfy all those condition?
First, I would create a method that checks if the entityToken is valid or not. If valid, use Observable.just() but you have to create an instance of Response somehow. If invalid, then call the server using the API in your requirement retrofitApi.authenticate(). Either path is taken, the method getTokenObservable() emits Observable<Response<EntityToken>>.
public Observable<Response<EntityToken>> getTokenObservable(EntityToken entityToken, String username, String password) {
boolean isTokenValid = preferenceHelper.isTokenValid(entityToken);
if (isTokenValid) {
//my assumption that you have something like this
Response<EntityToken> responseToken = new Response<EntityToken>();
responseToken.setEntityToken(entityToken);
return Observable.just(new Response<EntityToken>(entityToken.class));
} else {
Observable<Response<EntityToken>> updateServerToken = retrofitApi.authenticate(username, password);
return updateServerToken;
}
}
and then when calling it, use flatMap() which take emisssions of Observable<Response<EntityToken>> and returns emissions of Observable<Response<EntityPoint>>. Subscribe and proceed as normal.
Observable<Response<EntityToken>> updatePointObservable = getTokenObservable(entityToken, username, password);
updatePointObservable
.flatMap(new Func1<Response<EntityToken>, Observable<Response<EntityPoint>>>() {
#Override
public Observable<Response<EntityPoint>> call(Response<EntityToken> responseToken) {
EntityToken entityToken = responseToken.getEntityToken(); //my assumption
saveTokenLocally(entityToken); //this is where you save your token locally, change to the right method that you have
Observable<Response<EntityPoint>> updateServerTokenObservable = retrofitApi.postPoint(point, entityToken); //pass your entityToken to the call?
return updateServerTokenObservable;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Response<EntityPoint>>() {
#Override
public void onCompleted() {
//your own logic
}
#Override
public void onError(Throwable e) {
//your own logic
}
#Override
public void onNext(Response<EntityPoint> entityPoint) {
//your own logic
}
});
As there is a dependency between the three calls, merge does not make any sense. instead, use flatMap:
Observable<Response<EntityPoint>> response =
retrofitApi.isTokenValid()
.flatMap(isValid ->
isValid
? Observable.just("")
: retrofitApi.authenticate(username,password)
.doOnNext(token -> doSomethingWithTheToken(token)
)
.flatMap(dummy -> retrofitApi.postPoint(point));
I understand that, by default, observables created by retrofit are "cold" observables.
I have this specific call to my server endpoint
#POST("oauth/token")
Observable<Token> signIn(#Field("username") String username, #Field("password") String password);
When I do:
public class LoginUseCase extends Subscriber<Profile> {
public void logIn(String username, String password) {
Subscription subscription = myApi.signIn(username, password)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this);
this.add(subscription);
}
}
I receive the onNext onError onComplete callbacks, as you would expect.
The problem arises when the login fails and I try again. Calling the login() method a second time doesn't trigger the http call, and I don't get any callbacks.
By the way, Im doing this on my onComplete() method
#Override
public void onCompleted() {
this.unsubscribe();
}
Is there a way to tell retrofit/rxandroid to re make the http call everytime I call myApi.signin(). Am I even approaching this the right way?
Notes:
- Im using dagger2 in my project and the myApi object is a singleton.
- I'm able to reproduce the error even when I use different username/pass configs between the first and second try
Once Subscriber#unsubscribe() is called that subscriber can never receive new values. You will need to recreate your subscriber each time you want to subscribe to a new observable.
What is happening is that in the call to Subscriber#add(Subscription) it sees that the subscriber has already been unsubscribed and immediately cancels the new subscription.