Retrofit sends multiple requests - java

I'm using retrofit 2 in my application. In application there's an authorization. All works right, but there is one problem still left. I have access_token and refresh_token. When I'm login in app I get my tokens. When my access_token expires I can get the new one with refresh token. If the refresh token expires too, I'm authorizing again for getting new tokens.
In my case when retrofit need to send call for updating my tokens, I sends data multiple times. What can be the cause of this?
private ApiClient(String endpoint) {
OkHttpClient.Builder client = new OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS);
client.addInterceptor(new Interceptor() {
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
request = request.newBuilder()
.header("Cache-Control", "public, max-age=0")
.build();
return chain.proceed(request);
}
});
supportopApi = new Retrofit.Builder()
.baseUrl(endpoint)
.client(client.build())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(SupportopApi.class);
}
public static synchronized void initializeInstance(String endpoint) {
if (instance == null) {
instance = new ApiClient(endpoint);
}
}
public static synchronized ApiClient getInstance() {
if (instance == null) {
throw new IllegalStateException("PentairAPIClient has not been initialized.");
}
return instance;
}
I don't want to add a big code here, so I can to add the update part too.
public void updateToken() {
//Perform this call if access token is expired
final ApiClient apiClient = ApiClient.getInstance();
SupportObjToken supportObjToken = new SupportObjToken();
//Here I'm setting my user information for request
supportObjToken.setGrantType("refresh_token");
supportObjToken.setClientId(SharedPreferencesManager.getInstance().getUserData().getClientId());
supportObjToken.setClientSecret(SharedPreferencesManager.getInstance().getUserData().getClientSecret());
supportObjToken.setRefreshToken(SharedPreferencesManager.getInstance().getUserData().getRefreshToken());
//This request calls multiple times (2-3 times or more)
Call<RefreshTokenActivation> newToken = apiClient.newToken(supportObjToken);
newToken.enqueue(new Callback<RefreshTokenActivation>() {
#Override
public void onResponse(Call<RefreshTokenActivation> call, Response<RefreshTokenActivation> response) {
if (response.isSuccessful()) {
String newAccessToken = response.body().getAccessToken();
String newRefreshToken = response.body().getRefreshToken();
Here I'm saving my updated access_token
UserData userData = SharedPreferencesManager.getInstance().getUserData();
SharedPreferencesManager.getInstance().removeUser();
userData.setAccessToken(newAccessToken);
userData.setRefreshToken(newRefreshToken);
SharedPreferencesManager.getInstance().setUser(userData);
tokenUpdaterCallback.updateToken();
} else {
if (response.code() == 401) {
//Perform this call if refresh token is expired
//This request is called multiple times too
Call<TokenActivation> token = apiClient.getToken("password",
SharedPreferencesManager.getInstance().getUserData().getClientId(),
SharedPreferencesManager.getInstance().getUserData().getClientSecret(),
SharedPreferencesManager.getInstance().getUserData().getEmail(),
SharedPreferencesManager.getInstance().getUserData().getPassword());
token.enqueue(new Callback<TokenActivation>() {
#Override
public void onResponse(Call<TokenActivation> call, Response<TokenActivation> response) {
if (response.isSuccessful()) {
String access_token = response.body().getAccessToken();
String refresh_token = response.body().getRefreshToken();
UserData userData = SharedPreferencesManager.getInstance().getUserData();
SharedPreferencesManager.getInstance().removeUser();
userData.setAccessToken(access_token);
userData.setRefreshToken(refresh_token);
SharedPreferencesManager.getInstance().setUser(userData);
//tokenUpdaterCallback goes to main class and repeats the authorization request
tokenUpdaterCallback.updateToken();
}
}
#Override
public void onFailure(Call<TokenActivation> call, Throwable t) {
Toast.makeText(context, "An error occurred", Toast.LENGTH_SHORT).show();
}
});
}
}
}
#Override
public void onFailure(Call<RefreshTokenActivation> call, Throwable t) {
Toast.makeText(context, "Response is not successful", Toast.LENGTH_SHORT).show();
}
});
}
What can be the problem?

Related

Not getting any data from Retrofit. Its sending empty request to server

I have a problem with Retrofit call. I had call to post data on server, call is giving me back 200 response that means call is successful, but it's not saving any data to database and returning message "request is empty" in server stacktrace. Getting no data in response.
Interface call
#Headers({"org-id: vuk"})
#POST("/dmp/user/loginwithotp")
Call<ResponseAPI> signInWithOTP(#Body RequestBody jsonObject);
Retrofit Call
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT))
.addInterceptor(loggingInterceptor)
.addNetworkInterceptor(new CacheInterceptor(mContext))
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl )
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.client(client)
.build();
return retrofit;
}
public static VuAPIServices geVuAPIServices() {
VuAPIServices vuAPIServices = getRetrofit().create(VuAPIServices.class);
return vuAPIServices;
}
Code for send call request and response call in activity
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("mobileNumber", mobileNumber);
RequestBody body = RequestBody.create(json.toString(), MediaType.parse("application/json;charset=UTF-8"));
Call<ResponseAPI> responseAPICall = ApiClient.geVuAPIServices().signInWithOTP(body);
responseAPICall.enqueue(new Callback<ResponseAPI>() {
#Override
public void onResponse(Call<ResponseAPI> call, retrofit2.Response<ResponseAPI> response) {
if(!response.isSuccessful()) {
Log.e("TAG", "response: "+new Gson().toJson(response.body()) );
}
}
#Override
public void onFailure(Call<ResponseAPI> call, Throwable t) {
Log.e("TAG", "onFailure: "+t.toString() );
}
});
} catch (JSONException e) {
e.printStackTrace();
}
}
Response POJO
#SerializedName("flag")
private int flag;
#SerializedName("message")
private String message;
#SerializedName("status")
private Boolean status;
#SerializedName("otp")
private String otp;
#SerializedName("locked")
private Boolean locked;
#SerializedName("firstTimeLogin")
private Boolean firstTimeLogin;
#SerializedName("firstLogin")
private Boolean firstLogin;
Getter and Setters...
Postman Image
What should I change in my code? I welcome every hint. The status i m getting is 200 but with empty request on server side.
Updated with Response Result
E/TAG: response: {"firstLogin":false,"firstTimeLogin":false,"flag":0,"fromInternalApp":false,"locked":false,"mobileNumber":"4455332266","noToken":false,"status":false}
It could be because you are sending a json object. You can try making a request object instead and send that:
public class RequestObject {
private String mobileNumber;
public RequestObject(String mobileNumber) {
this.mobileNumber = mobileNumber;
}
public String getMobileNumber() {
return mobileNumber;
}
public setMobileNumber(String mobileNumber) {
this.mobileNumber = mobileNumber;
}
}
And then send it in the request:
#Headers({"org-id: vuk"})
#POST("/dmp/user/loginwithotp")
Call<ResponseAPI> signInWithOTP(#Body RequestObject requestObject); // here
I have solved my problem it was a problem that i needed to send a host in header of okhttp interceptor
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("User-Agent", System.getProperty("http.agent"))
.addHeader("Host", "localhost:8080")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
});
Soon after i added this in interceptor the problem solved and it got the response returned successfully.
E/TAG: response: {"firstLogin":false,"firstTimeLogin":false,"flag":1,"fromInternalApp":false,"locked":false,"message":"true OTP sent successfully on mobile number for Initial Login","noToken":false,"otp":"573287","status":false}
Thanks #rya for your valuable efforts with you tried to help me.

whenever i acces data from network using retrofit it works fine but in 2nd time can't find data through network using retrofit

I am trying to access data from the network (data is in the form of gson and i am using WordPress rest API) using retrofit but can't access. it shows me an error like data is null looks like retrofit can't find data but everything is good... looks like code is good and i don't know how to solve this. please help me I am a new developer.. it takes my 3 days
whenever i call getRetrofit() method it works fine... but when i call getImageRetrofit() then looks like this method won't work...this method return null value as shown in the logcat :
ImageInfo: info: null
private void getRetrofit() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
RetrofitArrayApi service = retrofit.create(RetrofitArrayApi.class);
Call<List<WPPost>> call = service.getPostInfo();
call.enqueue(new Callback<List<WPPost>>() {
#Override
public void onResponse(Call<List<WPPost>> call, Response<List<WPPost>> response) {
Log.e("Latest","response: "+response.body());
for (int i=0; i<response.body().size(); i++)
{
Log.e("main ","title "+response.body().get(i).getTitle().getRendered() + " " +
response.body().get(i).getId() );
String tempDate = response.body().get(i).getDate();
tempDate = tempDate.replace("T"," ");
String tempImageHref = response.body().get(i).getLinks().getWpFeaturedmedia().get(0).getHref();
Log.e("Href", "onResponse: "+tempImageHref);
String link = response.body().get(i).getLink();
Log.e("PostLink",link);
getImageRetrofit(tempImageHref);
list.add(new LatestModel(
response.body().get(i).getTitle().getRendered(),
tempDate,
tempImageHref,
LatestModel.IMAGE_TYPE,
response.body().get(i).getLink()
)
);
}
adapter.notifyDataSetChanged();
}
#Override
public void onFailure(Call<List<WPPost>> call, Throwable t) {
t.printStackTrace();
}
});
}
private void getImageRetrofit(String ImageHref) {
Log.e("getImageRetrofit","called "+ImageHref);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
RetrofitArrayApi service = retrofit.create(RetrofitArrayApi.class);
Call<List<WPPostImage>> callImage = service.getImageInfo(ImageHref);
callImage.enqueue(new Callback<List<WPPostImage>>() {
#Override
public void onResponse(Call<List<WPPostImage>> call, Response<List<WPPostImage>> response) {
Log.e("ImageInfo","info: "+response.body());
}
#Override
public void onFailure(Call<List<WPPostImage>> call, Throwable t) {
Log.e("Link Failed: ",": t.printStackTrace()" );
}
});
}
here is my RetrofitArrayApi Interface.:
public interface RetrofitArrayApi {
#GET("wp-json/wp/v2/posts?per_page=4")
Call<List<WPPost>> getPostInfo();
#GET("{id}")
Call<List<WPPostImage>> getImageInfo(#Path("id") String ImageHref); }
You said at comments that temImageHref: mubashirsaddique.com/wp-json/wp/v2/media/1780 and also your base url is baseUrl = "mubashirsaddique.com". So you send a request to this address mubashirsaddique.com/mubashirsaddique.com/wp-json/wp/v2/media/1780 when call getImageInfo.
Change your getPostInfo service. It should return just id(1780 in your case) as href value and modify RetrofitArrayApi.
#GET("wp-json/wp/v2/media/{id}")
Call<List<WPPostImage>> getImageInfo(#Path("id") String ImageHref);

Refresh Access Token Retrofit2 + RXJava2

This approach always worked when updating a token. That is, with each request if I received an error 401, the operator retryWhen() triggered it updated the token.
Here is the code:
private Observable<TokenModel> refreshAccessToken() {
Map<String, String> requestBody = new HashMap<>();
requestBody.put(Constants.EMAIL_KEY, Constants.API_EMAIL);
requestBody.put(Constants.PASSWORD_KEY, Constants.API_PASSWORD);
return RetrofitHelper.getApiService().getAccessToken(requestBody)
.subscribeOn(Schedulers.io())
.doOnNext((AccessToken refreshedToken) -> {
PreferencesHelper.putAccessToken(mContext, refreshedToken);
});
}
public Function<Observable<Throwable>, ObservableSource<?>> isUnauthorized (){
return throwableObservable -> throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) (Throwable throwable) -> {
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
if (httpException.code() == 401) {
return refreshAccessToken();
}
}
return Observable.error(throwable);
});
}
I call isUnauthorized() at the retryWhen() operator where I make a request to the server
class RetrofitHelper {
static ApiService getApiService() {
return initApi();
}
private static OkHttpClient createOkHttpClient() {
final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(chain -> {
Request originalRequest = chain.request();
AccessToken accessToken= PreferencesHelper.getAccessToken(BaseApplication.getInstance());
String accessTokenStr = accessToken.getAccessToken();
Request.Builder builder =
originalRequest.newBuilder().header("Authorization", "Bearer " + accessTokenStr);
Request newRequest = builder.build();
return chain.proceed(newRequest);
});
return httpClient.build();
}
private static ApiService initApi(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants._api_url)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(createOkHttpClient())
.build();
return retrofit.create(ApiService.class);
}
}
But we recently added Basic Auth, and now at the first request I get 401 and retryWhen() tries to update the Token, but still gets 401. That is, the doOnNext() does not work, but immediately the onError() works
private static Observable<AccessToken> refreshAccessToken() {
return RetrofitHelper.getApiService()
.getAccessToken(
Credentials.basic(
Constants._API_USERNAME, Constants._API_PASSWORD
),
Constants._API_BODY_USERNAME,
Constants._API_BODY_PASSWORD,
Constants._API_BODY_GRANT_TYPE
)
.doOnNext((AccessToken refreshedToken) -> {
PreferencesHelper.putObject(BaseApplication.getInstance(), PreferenceKey.ACCESS_TOKEN_KEY, refreshedToken);
}
});
}
// Api Service
public interface ApiService {
// Get Bearer Token
#FormUrlEncoded
#POST("oauth/token")
Observable<AccessToken> getAccessToken(#Header("Authorization") String basicAuth,
#Field("username") String username,
#Field("password") String password,
#Field("grant_type") String grantType);
}
Here, tell me why this is a mistake? Why at the first request I get 401, and from the second request everything works?
I want to suggest a better solution.
public class RefreshTokenTransformer<T extends Response<?>> implements ObservableTransformer<T, T> {
private class HttpCode {
private static final int UNAUTHORIZED_HTTP_CODE = 401;
}
private ApiService mApiService;
private UserRepository mUserRepository;
public RefreshTokenTransformer(ApiService service, UserRepository userRepository) {
mApiService = service;
mUserRepository = userRepository;
}
#Override
public ObservableSource<T> apply(final Observable<T> stream) {
return stream.flatMap(new Function<T, ObservableSource<T>>() {
#Override
public ObservableSource<T> apply(T response) throws Exception {
if (response.code() == HttpCode.UNAUTHORIZED_HTTP_CODE) {
return mApiService.refreshToken(mUserRepository.getRefreshTokenHeaders())
.filter(new UnauthorizedPredicate<>(mUserRepository))
.flatMap(new Function<Response<TokenInfo>, ObservableSource<T>>() {
#Override
public ObservableSource<T> apply(Response<TokenInfo> tokenResponse) throws Exception {
return stream.filter(new UnauthorizedPredicate<T>(mUserRepository));
}
});
}
return stream;
}
});
}
private class UnauthorizedPredicate<R extends Response<?>> implements Predicate<R> {
private UserRepository mUserRepository;
private UnauthorizedPredicate(UserRepository userRepository) {
mUserRepository = userRepository;
}
#Override
public boolean test(R response) throws Exception {
if (response.code() == HttpCode.UNAUTHORIZED_HTTP_CODE) {
throw new SessionExpiredException();
}
if (response.body() == null) {
throw new HttpException(response);
}
Class<?> responseBodyClass = response.body().getClass();
if (responseBodyClass.isAssignableFrom(TokenInfo.class)) {
try {
mUserRepository.validateUserAccess((TokenInfo) response.body());
} catch (UnverifiedAccessException error) {
throw new SessionExpiredException(error);
}
}
return true;
}
}
}
I`ve written the custom operator, which makes next actions:
first request started, and we get 401 response code;
then we execute /refresh_token request to update the token;
after that if the token is refreshed successfully, we repeat the
first request. if /refresh_token token is failed, we throw exception
Then, you can easy implement it in the any request like that:
Observable
.compose(new RefreshTokenResponseTransformer<Response<{$your_expected_result}>>
(mApiService, mUserRepository()));
One more important thing:
Most likely, that your initial observable for retrofit has params, like that:
mApiService.someRequest(token)
if the param is expected to change during the performing RefreshTokenTransformer(e.g. /refresh_token request will get new access token and you save it somewhere, then you want to use a fresh access token to repeat the request) you will need to wrap your observable with defer operator to force the creating of new observable like that:
Observable.defer(new Callable<ObservableSource<Response<? extends $your_expected_result>>>() {
#Override
public Response<? extends $your_expected_result> call() throws Exception {
return mApiService.someRequest(token);
}
})
I think it does not need to use interceptor instead you implement Authenticator by which you can access refreshed token and okhttp automatically will handle that. if you get 401 it updates header with refreshed token and make new request.
public class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}

How to Change API Base Url at Runtime(Retrofit,Android,Java)?

I need to change base url at run time.
I have login button and when login button click time i am called my login api
like below :
login api = http://192.168.0.61/api/authenticate
API_BASE_URL = http://192.168.0.61/api/
when i get success response from first api i get client server url for changing baseUrl.
CompanyUrlConfigEntity companyUrlConfigEntity = response.body();
like below :
String clientUrl = companyUrlConfigEntity.
getBaseUrl();
clientUrl = http://192.168.0.238/api/
In this project mainly for client or company based.So they have their own server.
Each company has using more than 20 api's.
So i need to change base url .
I am also checked below link for changing base url:
https://futurestud.io/tutorials/retrofit-2-how-to-change-api-base-url-at-runtime-2
and changed code like that
public static void changeApiBaseUrl(String newApiBaseUrl) {
API_BASE_URL = newApiBaseUrl;
builder = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new Gson()));
}
when i debugged and checked my baseUrl then it shows properly like below:
API_BASE_URL = http://192.168.0.238/api/
But when i call my customer api it shows the my first base url calling,
the url not changed.
expected customer api : http://192.168.0.238/api/customers
reality customer api : http://192.168.0.61/api/customers
I am also checked below link :
https://futurestud.io/tutorials/retrofit-2-how-to-use-dynamic-urls-for-requests
thats working , But each api need to pass fullPath url with each api like below:
#GET
public Call<ResponseBody> profilePicture(#Url String url);
But using this method , each api calling place i need to attach full path of url.
There is any other options? Please help me.
ServiceGenerator.class
public class ServiceGenerator {
public static String API_BASE_URL = "http://192.168.0.61/api/";
private static Retrofit retrofit;
private static OkHttpClient.Builder httpClient = new
OkHttpClient.Builder();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new
Gson()));
private ServiceGenerator() {
}
public static void changeApiBaseUrl(String newApiBaseUrl) {
API_BASE_URL = newApiBaseUrl;
builder = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new Gson()));
}
public static Retrofit retrofit() {
return retrofit;
}
public static <S> S createService(Class<S> serviceClass) {
return createService(serviceClass, null, null);
}
public static <S> S createService(Class<S> serviceClass,
final String authToken,
final ProgressListener progressListener) {
if (authToken != null) {
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
final String headerValue = AUTHORIZATION_TYPE + authToken;
Request request = original.newBuilder()
.header(AUTHORIZATION_HEADER_KEY, headerValue)
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
});
}
addResponseProgressListener(progressListener);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor httpLoggingInterceptor = new
HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(httpLoggingInterceptor);
}
if (authToken != null) {
if (picasso == null) {
setUpPicasso(authToken);
}
}
OkHttpClient client = httpClient.build();
httpClient.connectTimeout(15, TimeUnit.SECONDS);
httpClient.readTimeout(2, TimeUnit.MINUTES);
httpClient.writeTimeout(2, TimeUnit.MINUTES);
retrofit = builder.client(client).build();
return retrofit.create(serviceClass);
}
}
LoginFragment.java
#OnClick(R.id.bt_login)
void onLogin() {
checkValidityOfUser();
}
private void checkValidityOfUser() {
final Login login = getLoginCredentials();
Call<CompanyUrlConfigEntity> callCheckValidity = dataProcessController.
getApiClient().
checkValidityOfUsers(login.getUsername());
callCheckValidity.enqueue(new Callback<CompanyUrlConfigEntity>() {
#Override
public void onResponse(Call<CompanyUrlConfigEntity> call,
Response<CompanyUrlConfigEntity> response) {
if (response.code() == 200) {
CompanyUrlConfigEntity companyUrlConfigEntity = response.body();
boolean status = companyUrlConfigEntity.isValidUser();
if (status) {
String baseUrls = companyUrlConfigEntity.
getBaseUrl();
baseUrls = baseUrls + "/api/";
ServiceGenerator.changeApiBaseUrl(baseUrls);
logins();
} else {
ToastHelper.show("please contact admin");
}
} else {
ToastHelper.show("" + response.code() + response.message());
}
}
#Override
public void onFailure(Call<CompanyUrlConfigEntity> call, Throwable t) {
ToastHelper.show("please contact admin");
}
});
}
private void logins() {
login = getLoginCredentials();
Call<Void> callLogin = dataProcessController.
getApiClient().
login(login);
callLogin.enqueue(new Callback<Void>() {
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
if (response.code() == 200) {
} else if (response.code() == 401) {
}
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
}
});
}
Base on your comments, I would say that you are correctly changing the API url on your builder, but that your second call still uses an instance of service where the url has not changed.
To explain a little more, from what I understand this is how everything gets executed:
when fragment is created, the apiClient is created and pointing to the first url
with dataProcessController.getApiClient() in your first call, you are getting the service that is pointing to the first url and then execute the call.
when the call is successful, you read the new url from result and update the ServiceGenerator with that new url. Then you execute the logins() method.
and in that method, you recall the dataProcessController.getApiClient() and do the second call with it. However, as you never redid apiClient = ServiceGenerator.createService(ApiClient.class);, the apiClient instance you are getting is still pointing to the first url, because it hasn't been notified that the url changed.
What I would try here, would be to change the method getApiClient() in your DataProcessController class to something like this:
public ApiClient getApiClient() {
apiClient = ServiceGenerator.createService(ApiClient.class);
return apiClient;
}
and see if this is work better.
Or if you don't want to regenerate the service inside that function, you can also do something like this:
public class DataProcessController {
private ApiClient apiClient = null;
private DataProcessController() {
regenerateClient();
}
public ApiClient getApiClient() { return apiClient; }
// add this to regenerate the client whenever url changes
public void regenerateClient() {
apiClient = ServiceGenerator.createService(ApiClient.class);
}
}
then, everytime you do change the url, do this:
ServiceGenerator.changeApiBaseUrl(baseUrls);
dataProcessController.regenerateClient();
and you should get a client that points to the correct url everytime you do dataProcessController.getApiClient()
https://segunfamisa.com/posts/firebase-remote-config
You should follow concept of firebase remote config. Here you dont need to store base Url in source code it will be retrieved from firebase config values which is stored at server of firebase.
// fetch
mRemoteConfig.fetch(3000)
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
#Override
public void onComplete(Task<Void> task) {
if (task.isSuccessful()) {
// update your base url here.
} else {
//task failed
}
}
});

Retrofit Singleton Instance causing a 403 Issue in API calls?

I have setup a retrofit2 singleton instance which is accessed by all API methods. However, upon logging out the user and re-signing in, all API calls fail with a 403. The likely cause of this is retrofit re-using the previous destroyed access token and not resetting the retrofit instance.
Singleton:
public class RetroGenerator {
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
private static Retrofit.Builder builder;
public static Retrofit retrofit;
static synchronized private Retrofit.Builder getBuilder() {
if (builder == null) {
initRetrofit();
}
return builder;
}
public static void initRetrofit() {
builder =
new Retrofit.Builder()
.baseUrl(SessionManager.getInstance().baseUrl)
.addConverterFactory(GsonConverterFactory.create(initGson()));
}
private static Gson initGson() {
return new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").create();
}
public static Retrofit getRetrofit() {
return getBuilder().client(httpClient.build()).build();
}
public static <S> S createService(Class<S> serviceClass) {
return getRetrofit().create(serviceClass);
}
public static <S> S createService(Class<S> serviceClass, final Auth auth) {
if (retrofit == null) {
if (auth != null) {
//Adding request payload logging
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(logging);
}
httpClient.retryOnConnectionFailure(true);
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.header("Accept", "application/json")
.header("Authorization",
auth.token_type + " " + auth.access_token)
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
}
OkHttpClient client = httpClient.build();
if (retrofit == null) {
retrofit = getBuilder().client(client).build();
}
}
return retrofit.create(serviceClass);
}
public static void clearRetrofit(){
retrofit = null;
}
}
An API class would use this by:
RetroGenerator.createService(APIServices.class).getUsers().enqueue(new Callback<ArrayList<User>> () {
#Override
public void onResponse(Call <ArrayList<User>> call, Response <ArrayList<User>> response) {
if (response.isSuccessful()) {
callBack.onSuccess(response.body());
}
}
#Override
public void onFailure(Call <ArrayList<User>> call, Throwable t) {
}
});
To amend this, my attempt was to set the retrofit instance as null upon signing out. Thus, the use of clearRetrofit() method. However, the issue still exists and setting it null didn't fix the issue. What is the reoolution for this?
I solved this problem. Here's the write-up:
Problem:
I needed global access to a retrofit instance without having to re-initialise an instance every time
The code in the question was confusing the public retrofit instance with authenticated endpoints thus leading to a 401: Unauthorised error.
Solution:
The solution I've attached below was written to deliver the following:
- Re-usability of a retrofit instance by using a Singleton design pattern
Ability to write an interface APIServices and include all endpoints in it. This allows extensibility.
Ability to switch between a retrofit built for public endpoints and a retrofit built for authenticated endpoints.
Here's my solution:
https://gist.github.com/dinukapj/b315e5f4438bc670d7509f7aa7aaffdd

Categories

Resources