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.
Related
I have subscribed an observer as follows
public PublishSubject<ChannelModel> publisher = PublishSubject.create();
publisher.subscribe(observer);
Observer<ChannelListModel> observer = new Observer<ChannelListModel>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(ChannelListModel model) {
adapter.setChannels(model.getChannels());
}
};
It works perfectly fine the first time I request data and display the same. But if request fails and as soon as the onError is called, the publisher will lose its observer. I want to re-subscribe to the same observer. So that when I retry the failed request, I can publish the result to the observer.
In order to re-subscribe an Observable on error, you should use Retry operator. In RxJava, you can do it as follows
publisher
.retry()
.subscribe(observer);
After that subscription should be retried infinitely as long as error will be thrown.
You can also specify limit of possible retries like that:
publisher
.retry(5)
.subscribe(observer);
In the example above, subscriber will retry operation 5 times. When the error will be still persisted after 5 tries, then subscription will fail.
I've been trying to figure out how to authenticate users for my android app. It is based on a website which already has a developed api, using JWT to authenticate.
I have come against the problem of refreshing tokens. Let's say I want to fetch something from the API and I need the auth token for that. I check my current auth token. If it is expired, I need to get a new one using some sort of refresh token.
However, it seems like almost no matter how I think of trying to implement it, I run into a few problems:
I don't want the UI thread to wait while I get a new token
I would prefer that I don't have to explicitly check whether the token
is there (and then refresh it) before making any API call
I've come up with one solution that solves #1 and at least minimizes the pain of #2. I can have some sort of getToken method. As an example, using JS style promises because they're easier for me to understand:
function getToken() {
return new Promise((resolve) => {
// Check for token, and return if valid.
// Otherwise, go to the server and get a new one
...
resolve(token)
}
}
// When making an API call
getToken().then((token) => {
// Call API
})
I think I can work this out so that the request will never be running on the UI thread, which solves #1, and as far as #2, it's at least bearable.
My question is this: is there a better way to do this? It kind of seems like AccountManager might be able to handle this sort of thing for me, but the documentation for it is subpar at best, so I'm not sure how I would even implement it. If AccountManager can do it and you know of a good tutorial for it, please comment with that.
A way to accomplish this is intercept a 401 status code and refresh token.
If you are using Volley, you can extend Request class and override parseNetworkEror(VolleyError error) method. If need be, schedule a Job which will refresh the token (JobDispatcher) and trigger an event to communicate UI about the change (EventBus).
The following example is using OAuth authentication, but can be easily changed to implement JWT.
#Override
protected VolleyError parseNetworkError(VolleyError volleyError) {
if (getDataAccess().shouldRefreshToken(volleyError)) {
if (!EventBus.getDefault().hasSubscriberForEvent(TokenRefreshedEvent.class)) {
EventBus.getDefault().register(this);
}
CSApplication app = CSApplication.getInstance();
FirebaseJobDispatcher dispatcher = app.getJobDispatcher(app.getApplicationContext());
Job myJob = dispatcher.newJobBuilder()
.setService(JobRefreshToken.class)
.setTag("REFRESH_TOKEN")
.setTrigger(Trigger.NOW)
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
.setConstraints(Constraint.ON_ANY_NETWORK)
.build();
int result = dispatcher.schedule(myJob);
if (result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS) {
LogUtils.log(LogUtils.Type.JOB, GsonRequest.class, "Scheduling job refresh token");
} else {
LogUtils.log(LogUtils.Type.JOB, GsonRequest.class, "Error on schedule refresh token");
}
}
return super.parseNetworkError(volleyError);
}
public boolean shouldRefreshToken(VolleyError error) {
boolean shouldRefreshToken = error.networkResponse != null && error.networkResponse.statusCode == 401;
if (shouldRefreshToken) {
Map<String, String> headers = error.networkResponse.headers;
if (headers.containsKey("WWW-Authenticate")) {
String value = headers.get("WWW-Authenticate");
boolean issuerInvalid = value.contains("The issuer is invalid");
shouldRefreshToken = !issuerInvalid;
if (issuerInvalid) {
log(LogUtils.Type.VOLLEY, DataAccess.class, "Issuer do token é inválido");
}
}
}
return shouldRefreshToken;
}
Job Code
getDataAccess().refreshToken(getApplicationContext(), new VolleyCallback<Void>() {
#Override
public void onSuccess(Void aVoid) {
EventBus.getDefault().post(new TokenRefreshedEvent(true));
job.jobFinished(params, false);
log(LogUtils.Type.JOB, JobRefreshToken.class, "Refresh Token job finished");
}
#Override
public void onError(VolleyError error) {
super.onError(error);
EventBus.getDefault().post(new TokenRefreshedEvent(false));
job.jobFinished(params, false);
}
});
return true;
}
What I ended up doing was creating a method getToken which either returns the current token or gets a new one (blocking). With this strategy, I need to make sure that it never gets called from the UI thread. I created a Retrofit2 interceptor which calls getToken. The benefit of this method is that I can just call my Retrofit methods without worrying about the token at all, and it checks for expiration and gets a new one as necessary.
I am using RxJava on Android to perform a login operation.
I need to pass in a username, password and a boolean flag. The username and password and sent to a server for verification and once a response is returned I need to use the flag to determine what to do next.
Since the login operation is asynchronous, I want to ensure that when the response returns that I will still have access to the username, password and flag that I passed in at the beginning.
Here is the initial way I coded this up that I believe has problems:
public Observable<Result> execute1(final String username, final String password, final boolean shouldSaveUsername) {
return mLoginNetwork
.loginWithCredentials(username, password)
.map(new Func1<Response<Void>, LoginObject>() {
#Override
public LoginObject call(Response<Void> response) {
if (!response.isSuccessful()) {
Exceptions.propagate(new HttpException(response));
}
return new LoginObject(username, password, shouldSaveUsername);
}
})
.doOnNext(new Action1<LoginObject>() {
#Override
public void call(LoginObject loginObject) {
if (loginObject.shouldSaveUsername) {
saveUsername(username);
}
}
})
.flatMap(new Func1<Entitlement, Observable<Result>>() {
#Override
public Observable<Result> call(LoginObject loginObject) {
return mNetwork
.fetchSomething();
}
});
}
When I call execute1() it returns an Observable which I cache and then subscribe to. If an Android configuration change occurs I unsubscribe from the Observable but keep it in a cache. Once the configuration change is complete I take the Observable out of the cache and resubscribe to it. When I resubscribe the loginWithCredentials call would need to be made again, but when it returns the username, password and boolean flag would no longer exist and therefore I wouldn't be able to use them in my chain which is a problem.
So, how to solve this issue?
I need a way for the input data to the Observable to become part of the Observable so that when I cache the Observable the input data is also cached.
Here is a proposed solution below:
public Observable<Result> execute2(String username, String password, boolean shouldSaveUsername) {
return Observable
.just(new LoginData(username, password, shouldSaveUsername))
.flatMap(new Func1<LoginData, Observable<LoginData>>() {
#Override
public Observable<?> call(final LoginData loginData) {
return mLoginNetwork
.loginWithCredentials(loginData.getUsername(), loginData.getPassword())
.map(new Func1<Response<Void>, LoginData>() {
#Override
public LoginData call(Response<Void> response) {
if (!response.isSuccessful()) {
Exceptions.propagate(new HttpException(response));
}
return loginData;
}
});
}
})
.doOnNext(new Action1<LoginData>() {
#Override
public void call(LoginData loginData) {
if (loginData.shouldSaveUsername) {
saveUsername(username);
}
}
})
.flatMap(new Func1<LoginData, Observable<Result>>() {
#Override
public Observable<Result> call(LoginData loginData) {
return mNetwork
.fetchSomething();
}
});
}
What I'm attempting to do is to make the input data part of the stream right away by using Observable.just() to take the input data and make it into an Observable and then let the rest of the downstream operations receive it as an input. I assume that if I now cache the observable and resubscribe later that the input data is now embedded in my observable and can be accessed in any of the operators later.
Have I solved my problem in my proposed solution in a "normal" RxJava / functional way? Are there better ways to approach this problem?
The username/password/save-flag are passed in to execute1() as parameters, marked as final. Then, in your anonymous nested classes, you make explicit references to those values, "closing over" them. The resulting observable chain has everything bound to it that it needs in order to operate.
Subscribing to the observable again will use the original username/password/save-flag.
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.*/
});
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));