I'm developping an app that pulls and push data to a server.
To prevent undesired use of the webservices, we settled a token system that renews every once in a while.
I'm creating a method that would check before every webservices calls if the token is not null.
Something like :
public boolean isTokenValid(){
if (token == null){
renewToken();
return false;
}
return true;
}
Same fashion if I get server error code that the token is expired I'd call renewToken()
My question :
How do I have my method to be called again when receiving the valid token ? Keep in mind the method called after is "dynamic" : the method could be called from any webservice and therefore require it to run this very call again.
Potential solutions :
1) Create as many method of token renewal as the webservices.
For eg : renewTokenGetProduct(), renewTokenGetProfile()... to know which webservice to call when getting valid token. But that seems rather inapropriate.
2) I could also be passing a parameter. For example renewToken(PRODUCT) or renewToken(PROFILE) and then use conditions or broadcast but it seemed to me a bit overkill/sloppy again.
From api side try to add one more parameter with the token.
Like token and category.
so
public boolean isTokenValid(){
if (token == null){
renewToken();
return false;
}
return true;
}
then in renew token:
if(category.equals("profile"){
//do necessary work
}
else if(category.equals("product"){
//do necessary work or pass intent
}
else{
//
}
Related
I am trying to implement a refresh token flow in RxJava2 and Kotlin and I have trouble handling errors. There are several requests that need to be done in sequence but the sequence differs in case there are some errors.
Basically, if I try to use my refresh token and receive 400 - Bad Requestresponse because the token is not valid, I need to terminate the flow and do not execute the next switchMap (and ideally, I would like to return the final Observable<ApplicationRoot>). But I am not sure how to achieve this.
If I use onErrorReturn, I will just pass the returned result to the next switch map. And doOnError just executes the step when the request fails but the whole sequence continues.
fun refreshToken(): Observable<ApplicationRoot> {
// try to use the refresh token to obtain an access token
return authRepository.refreshToken(token)
.switchMap { response ->
// process response here
userRepository.getUser() // fetch user details
}.doOnError {
// TODO - return final result, do not jump to next switchMap
// refresh token is not valid -> go to login screen
Observable.just(ApplicationRoot.LOGIN) // not working
}.switchMap { response -> // excpects response of type UserResponse
// save user details here
}
}
Does anyone know who to jump out of the sequence of switch maps if some error occurs?
Probably, you should do something like this:
fun refreshToken(): Observable<ApplicationRoot> {
return authRepository.refreshToken(token)
.switchMap { response ->
userRepository.getUser()
}
.switchMap { response -> // do something with user response
}
.map { ApplicationRoot.ROOT_THAT_MEANS_SUCCESS }
.onErrorResumeNext(Observable.just(ApplicationRoot.LOGIN))
}
I am not aware about implementation of authRepository.refreshToken and how do responses look like, but in case if response is your custom object rather than retrofit2.Response<T> it should work.
I'm implementing codes with jwt on Android.
At point of using refresh token, I'm not sure my code is correct way.
Here is sequene diagram of my flow.
Server issued access token and refresh token. These expire time is 1hour and 3 days. These token is saved to sharedpreferences.
Here is above diagram's description.
When access token is expired, http call will be failed with 401 error.
So I implemented getAccessToken() for re-newing access token.
(1) : One AsyncTask is used for this whole http call step.
- My AsyncTask is too big, I want to refactor it.
(2) : (1)'s AynsTask has a logic for re-getting access token.
- This logic was duplicated all my HTTP call functions.
(3) : After renewing access token, my app re-try to call /api/foo
- To retry it, AsyncTask's doBackground() function is call recursivly.
Here is my code snippet.
class ApplyCheck extends AsyncTask<String, Void, ResponseTypeEnum> {
private List<ApplyEntity> applyEntityList = null;
#Override
protected ResponseTypeEnum doInBackground(String... strings) {
try {
response = restManager.getApplyList(strings[0],"","",""); // call /api/foo
} catch (RestRuntimeException e) {
return ResponseTypeEnum.SERVER_ERROR;
}
switch (response.code()) {
case 200:
//set applyEntityList
....
return ResponseTypeEnum.SUCCESS;
case 401:
//<-- This routine is duplcated all my AsyncTasks
if(getAccessToken()) {
//<-- recursive call to re-call api
return doInBackground(strings);
} else {
return ResponseTypeEnum.TOKEN_EXPIRE;
}
}
}
//re-issue new access token
private boolean getAccessToken() {
Response response = restManager.getAccessToken(); // call /auth/issue-token
if(response.code() == 200) {
String tokens = response.body().string();
JSONObject jsonObject = new JSONObject(tokens);
sharedPreferences.edit().putString("accessToken", jsonObject.getString("accessToken"));
sharedPreferences.edit().putString("refreshToken", jsonObject.getString("refreshToken"));
return true;
} else {
return false;
}
My Questions
1. Is my approach correct? If not, please inform me good practice.
2. If yes, are any good practice for extracting common function for my duplicated AsyncTasks?
The process you have is fine IMHO. The only change is that I would not recursively call doInBackground. What you're doing is feasible, but it violates the intention of doInBackground. Rather modify your AsyncTask to cope with processing different responses in onPostExecute, (ie chaining your requests), and call the AsyncTask again with the relevant parameters for each use case. It will make it much easier to maintain as you can add specific methods to the AsyncTask to cope with each response type and can see how it's triggered in a linear way. If you need to update onProgressUpdate, you should also pass a progress value to the chained AsyncTask calls so it can maintain consistency on the progress. Otherwise it would keep restarting on each call.
I have an issue with my network client design. I have a use case, when the client tries to request an item from a REST API, but in case the API returns a 404 HTTP status code I need to send a request to create the item on the server and then request the item again.
I would like to use RxJava to avoid the callback hell. Is this a valid use case RxJava? Is it possible to create such a conditional sub-request?
Thank you for your time and answers.
Based on your question, I assume you have something that look like
public Observable<Item> getItem();
that will either return the item, or fire an error and
public Observable<?> createItem();
That will create one.
You can use those two together like so:
public Observable<Item> getOrCreateItem() {
return getItem().onErrorResumeNext(error -> {
// Depending on your framework, figure out which is the result code
if (error.getResultCode() == 404) {
return createItem().flatMap(ignored -> getItem());
} else {
return Observable.error(error);
}
});
}
With Retrofit, you'd have to simply make sure the exception is a RetrofitError, cast it, and get the response and the status code. (((RetrofitError) error).getResponse().getStatus())
Im starting to get crazy on my problem here.
I have a main activity that connects to a server on onCreate event, (the program must be connected to the server or else it shall not be working).
client_thread = new ServerCom(this);
Now I want to create a login view with (Username, Password and Login buttom with a user exist validation).
And when there is a login view there has to be some sort of registration view (with Username, Password, confim Password and a Registration button with a user exist validation).
I have create a Login and Registration Activity (for handeling some data with login and registration).
public ClassLogIn(MainActivity owner)
{
this.owner = owner;
}
public ClassLogIn()
{
}
public void onClick(View view) {
if(view == btnLogIn)
{
if(TextUtils.isEmpty(userName.getText()) == false) {
if (TextUtils.isEmpty(userPassword.getText()) == false) {
owner.LogIn(userName.getText(),userPassword.getText());
}
...
To start the activitys from my mainActivity I have done this
else if(id == R.id.action_login){
Intent myIntent = new Intent(MainActivity.this, ClassLogIn.class);
startActivity(myIntent);
}
else if(id == R.id.action_registera){
Intent myIntent = new Intent(MainActivity.this, ClassRegistera.class);
startActivity(myIntent);
}
else if(id == R.id.action_Logout)
{
client_thread.LogOut();
}
Now my problem is how can I get the userName value to my client_thread?
And also how can I call Owner.LogIn from my ClassLogIn activity?
public boolean LogIn(Editable UserName, Editable Password)
{
return client_thread.LogIn(UserName.toString(),Password.toString());
}
Or do I trying to make something that is impossible in Android?
From the top of my head set up some IntentServies to catch intents and make then
set up your variables.
And static variables are not an option ?
(Would add a comment but don't have enough reps)
The specific code to do the following can differ depending on your specifics.
But in general, the procedure we do is the following:
NOTE - We use the WorkingStorage API to save parameters directly into the HHU (Hand Held Unit)
When we initially launch we make an attempt to retrieve from within the HHU the login credentials which includes an Authorization ID.
If they are present, we utilize the Authorization ID for subsequent WebService calls as needed throughout the Activities.
If they are Not present, we launch a User Data Entry 'screen' and get Username & Password input.
Then we send that data to the Server via a WebService call for validation.
The WebService responds to the HHU with a Result.
That Result is either a Valid Authorization ID or an Error Message (such as 'Not Valid', etc.).
If it is a Valid Authorization ID, then all of the login credentials are saved to the HHU with the WorkingStorage API and are then available to be utilized for subsequent WebService calls.
I'm developing an Android app using Gigya to allow people to register using Facebook and Twitter; in parallel another developer is doing the same thing in iOS. We want to implement custom login UI.
The standard method uses Gigya's own UI and is documented here:
http://developers.gigya.com/035_Mobile_SDKs/020_Android#Logging_in_the_User
Beneath, it simply suggests:
If you wish to implement the graphic design by yourself, use the login method instead.
The standard login method calls a dedicated post-login callback with an onLogin(...) method and all subsequent flows are described as stemming from this event. The other login method calls a standard onGSResponse(...) callback; it's not clear how the response can be used to construct a user so I've set up my implementation to call socialize.getUserInfo. Attempts to call either method have resulted in lots of unusual errors.
As per the Gigya instructions I'm starting up with
mGSAPI = new GSAPI(GIGYA_APP_KEY, this);
mGSAPI.setAPIDomain("eu1.gigya.com");
in onCreate(...) (where GIGYA_APP_KEY is a value copied from our console). I'm calling setAPIDomain because we were getting an invalid data center error (albeit with a 500001 code, not a 301001 code!), which this has fixed.
Facebook login goes through the login flow as I'd expect and then comes back with error 400093 (which the docs tell me is an invalid API parameter, and has the message " Missing parameter: client_id").
Twitter login comes back with 206002, " Account Pending Verification", which seems to make sense; I then call
mGSAPI.sendRequest(
"getUserInfo",
null, //parameters
true, //use HTTPS
this, //the callback
null //a context object
);
and this gives me the error:
Missing required parameter: No secret or signature were provided. Request could not be verified.
The documentation for socialize.getUserInfo suggest a UID is required for web apps, but not for native ones. It mentions no other mandatory fields. I am a bit stuck ... shouldn't the GSAPI object be handling verification, as it's initialized with the API key?
I can give you some direction at a very high level for integrating GIGYA. (Code below is not verbatim) Hopefully it is somewhat helpful.
For a private Android app I had created a Manager object (GigyaManager) that maintained a singleton instance of the GSAPI object.
This singleton GigyaManager was initialized in my application object:
public static GigyaManager getInstance(String apiKey, Context context) {
mGSAPI = new GSAPI(apiKey, context);
}
My GigyaManager class also had a wrapper method for handling the login w/social services:
public void loginWithSocialService(GigyaSocialProvider provider, GSResponseListener listener) throws Exception {
// did the user attempt a social login, and bail out on the registration
// phase?
if (GigyaManager.getInstance().getGSAPI().getSession() != null) {
logout();
}
GSObject providerArgs = new GSObject();
providerArgs.put(GigyaManager.GIGYA_ARG_PROVIDER, provider.name().toLowerCase());
mGSAPI.login(providerArgs, listener, null);
}
This was fired from an onClick listener in a fragment that contained a "login" button:
GigyaManager.getInstance("appKey", getActivity()).loginWithSocialService(GigyaSocialProvider.FACEBOOK, this);
That fragment had to implement GSResponseListener that has the callbacks to deal with whether the login was successful or not:
#Override
public void onGSResponse(String method, GSResponse response, Object context) {
if (!method.equalsIgnoreCase("login") || response.getErrorCode() != 0) {
return;
}
GIGYAResponseWrapper resp = new GIGYAResponseWrapper(response.getResponseText());
// user is attached to login provider?
if (resp.isIsAttached()) {
// start some sort of loader or asynctask to get information about user account
// connected to GIGYA social login
Bundle args = new Bundle();
args.putString(ARG_UID, resp.getUid());
args.putString(ARG_UID_SIGNATURE, resp.getUidSignature());
args.putString(ARG_SIGNATURE_TIMESTAMP, resp.getSignatureTimestamp());
args.putString(ARG_SOCIAL_NICKNAME, resp.getNickname());
} else {
// login success, but this social account is not associated with anything in GIGYA
}
}