Java - Passing variable to an interface - android

I am building an android app and i am using Retrofit to retrieve data from API. In this app i have to make 3 calls. The first one is working fine. The code for the first one is below. I have one class
public class APIClient {
private static Retrofit retrofit = null;
static Retrofit getClient(){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
retrofit = new Retrofit.Builder()
.baseUrl("https://api_app.com")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
}
Also i have this interface
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0"
})
#POST("/users/login")
Call<MainUserLogin> logInUser(#Body LoginBody loginBody);
The code of the Actvity is this
call.enqueue(object : Callback<MainUserLogin> {
override fun onResponse(call: Call<MainUserLogin>, response: Response<MainUserLogin>) {
if (response.code().toString().equals("200")){
val resource = response.body()
bearerToken = resource.session.bearerToken
if (bearerToken.isNotEmpty() && bearerToken.isNotBlank()){
val sharedPreferences = getSharedPreferences("Settings", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putString("bearerToken", bearerToken)
editor.commit()
BearerToken.bearerToken = bearerToken
val i = Intent(this#LoginActivity, UserAccountsActivity::class.java)
i.putExtra("bearerToken", bearerToken)
startActivity(i)
}else{
Toast.makeText(applicationContext, "Please try again.", Toast.LENGTH_LONG).show()
}
}else{
println("edwedw "+response.errorBody().string())
Toast.makeText(applicationContext, "Incorrect email address or password. Please check and try again.", Toast.LENGTH_LONG).show()
}
}
override fun onFailure(call: Call<MainUserLogin>, t: Throwable) {
call.cancel()
}
})
This call is working fine.
With this call i am getting one token. The problem is that i have to pass this token as header to make the second call. So, the second call will be like this.
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0",
"Authorization: "+***Token***
})
#GET("/products")
Call<MainUserLogin> getUseraccounts ();
Is there any way to pass the variable from the Activity to the interface to make the Api request?
Thank you very much.

Using Retrofit you can call API's with multiple headers as follows
#GET("/products")
Call<MainUserLogin> getUseraccounts(#Header("AppId") String appId, #Header("Content-Type") String contentType, #Header("appVersion") String appVersion, #Header("apiVersion") String apiVersion, #Header("Authorization") String token);
Instead of
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0",
"Authorization: "+***Token***
})
#GET("/products")
Call<MainUserLogin> getUseraccounts ();
this. When you call getUseraccounts method you can parse the token that you created from the previous endpoint.
Try this and let me know your feedback. Thanks!

Once you receive the token, you should save this token in a global repository since the auth token is something that your app will need in order to make further authenticated api calls.
After that, define a AuthorizationHeaderInterceptor which will extend okhttp3.Interceptor. Override the intercept method of this interceptor to add auth token to your request.
#Override
public Response intercept(#NonNull Chain chain) {
return completeRequest(chain);
}
private Response completeRequest(#NonNull Interceptor.Chain chain) {
AuthToken authToken = authTokenRepository.get();
Request.Builder requestBuilder = chain.request().newBuilder();
if (authToken != null && chain.request().header(Authorization.NAME) == null) {
requestBuilder.addHeader(Authorization.NAME, Authorization.getValue(authToken.getIdToken()));
}
Request request = requestBuilder.build();
try {
return chain.proceed(request);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
The interceptor can be added when you build your okhttpClient.
okHttpClientBuilder.addInterceptor(new AuthorizationHeaderInterceptor(authTokenRepository))
Note that the Authorization class is simple convenience class which encapsulates the authorization header name and value format.
public class Authorization {
public static final String NAME = "Authorization";
#NonNull
public static String getValue(#NonNull String accessToken) {
return String.format("Bearer %s", accessToken);
}
}

Related

Best practices of using Retrofit2 (multiple service)

I'm writing an Android app using Kotlin, integrating Retrofit2.
As far as I understand (please correct me if I'm wrong), the "traditional" way of doing this, would be:
Creating an interface, which includes method definitions for all my APIs.
Passing it to retrofit using retrofit.create(), which implements it for me, and then I have access to all of them using the functions from step #1
After looking at this, my question is:
Is it a better practice to create a separate interface for each of my requests?
e.g. If I have a "LoginRequest", and implement it as I show below (the "create" essentially calls retrofit.create()), the next time I want to add/remove an API I only need to add/remove 1 file, rather than a few places (the request itself, the service from step #1, and all the places that use the methods from step #2). On the other hand, this would cause my app to "know" retrofit2, and I'm not sure this is a good practice either.
interface MyRequest {
fun execute()
}
class LoginRequest (private val email: String, private val password: String) : MyRequest {
interface LoginRequestService {
#POST("login")
fun emailLogin(
#Body loginRequestBody: LoginRequestBody):
retrofit2.Call<GetUserDetailsResponse>
}
override fun execute() {
val requestBody = LoginRequestBody(email, password)
val call = MyRequestManager.create(LoginRequestService::class.java).emailLogin(requestBody)
MyRequestManager.executeCall(call)
}
}
it would be much better if you follow the official guide https://square.github.io/retrofit/
public interface GitHubService {
#GET("users/{user}/repos")
Call<List<Repo>> listRepos(#Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("octocat");
then you can have one Singleton for the Service for easier, I like also to create interfaces for each call like bellow that will be used in any class that I want.
interface IListRepos {
fun listRepos(user: String, onResponse: (MutableList<Repo>?) -> Unit) {
ServiceSingleton.client.create(GitHubService::class.java)
.listRepos(user)
.enqueue(object : Callback<MutableList<Repo>> {
override fun onResponse(call: Call<MutableList<Repo>>,
response: retrofit2.Response<MutableList<Repo>>) {
onResponse(response.body())
}
override fun onFailure(call: Call<MutableList<Repo>>, t: Throwable) {
onResponse(null)
}
})
}
}
The way im doing it in java
is having 1 interface for all, with separate requests.
Inside
public interface ApiInterface {
}
i have set all the urls in 1 place for easy edit later on
String Base_Url = "http://url.com/store/web/app_dev.php/api/";
String Base_Url_Channel = "http://url.com/store/web/app_dev.php/api/APP_STORE/";
String Image_URL_Online = "http://url.com/store/web/media/image/";
and for retrofit2 method call class
public class DataServiceGenerator {
public static <S> S createService(Class<S> serviceClass) {
String url = ApiInterface.Base_Url;
Retrofit.Builder builder = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(url);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder()
.readTimeout(15, TimeUnit.SECONDS)
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(25, TimeUnit.SECONDS);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(interceptor);
httpClient.addNetworkInterceptor(new StethoInterceptor()); // for debugging
}
builder.client(httpClient.build());
Retrofit retrofit = builder.build();
return retrofit.create(serviceClass);
}
}
Now to call the API im using below method in interface
#Multipart
#Headers("Accept: Application/json")
#POST("oauth/v2/token")
Call<Token_Model> token(
#Part("client_id") RequestBody id,
#Part("client_secret") RequestBody secret,
#Part("grant_type") RequestBody username,
#Part("username") RequestBody name,
#Part("password") RequestBody password);
And for Method itself :
Call<Token_Model> call = service.token(createPartFromString("13123khkjhfsdf"),
createPartFromString("1asd234k234lkh24"),
createPartFromString("password"), createPartFromString("api#example.com"), createPartFromString("test"));
call.enqueue(new Callback<Token_Model>() {
#Override
public void onResponse(Call<Token_Model> call, retrofit2.Response<Token_Model> response) {
if (response.isSuccessful()) {
token_model = response.body();
if (token_model != null) {
helper.setToken(token_model.getAccess_token());
}
} else {
Toast.makeText(context, context.getString(R.string.failed_token), Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(Call<Token_Model> call, Throwable t) {
Toast.makeText(context, context.getString(R.string.failed_token), Toast.LENGTH_LONG).show();
}
});

Retrieve SharedPreferences in retrofit2 interface

I'm trying to add programmatically an authorization header to my api calls with retrofit and rxjava2 so i added an okhttp3 interceptor to it. This is my full code:
public interface APIService {
class ServiceInterceptor implements Interceptor{
#NonNull
#Override
public okhttp3.Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
if (request.header("No-Authentication") == null){
SharedPreferences sharedPref = ???.getSharedPreferences(USER, Context.MODE_PRIVATE); <---
request = request.newBuilder()
.addHeader("Authorization", "JWT " + sharedPref.getString("auth_token", null))
.build();
}
return chain.proceed(request);
}
}
OkHttpClient apiClient = new OkHttpClient().newBuilder()
.addInterceptor(new ServiceInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.1.8:8000/api/v1/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(apiClient)
.build();
#GET("rest-auth/user/")
Single<Response<User>> getUserDetails();
#POST("rest-auth/login/")
#Headers("No-Authorization: true")
Single<Response<AuthUserResponse>> loginUser(#Body LoginRequest body);
#POST("rest-auth/registration/")
#Headers("No-Authorization: true")
Single<Response<AuthUserResponse>> signupUser(#Body SignupRequest body);
}
The problem is that i can't retrieve the auth_token from sharedPreferences because i don't know how to pass the context to it. Any suggestions?
Thanks.
You need to create a constructor for your ServiceInterceptor class, passing the auth_token as a param, something like
private String authToken;
public ServiceInterceptor(String authToken) {
this.authToken = authToken;
}
That way you handle the context and SharedPreferences outside your interceptor class, it shouldn't know about android stuff anyway.
Then on your intercept method you can get the authToken.

How to send post request with basic auth in retrofit?

In my code, I want to send post request with basic auth.
Here is my postman screenshot :
here is my apiInterface class
#FormUrlEncoded
#POST("GetBarcodeDetail")
Call<PreliminaryGoodsAcceptResponse> PRELIMINARY_GOODS_ACCEPT_RESPONSE_CALL(#Field("ProcName") String procName, #Field("Barcode") String barcode, #Field("LangCode") String langCode);
here is my apiclient
public class ApiClient {
public static final String BASE_URL = "http://192.**********";
private static Retrofit retrofit = null;
private static OkHttpClient sClient;
public static Retrofit getClient() {
if(sClient == null) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
sClient = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEFAULT))
.addInterceptor(interceptor)
.build();
}
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(sClient)
.build();
}
return retrofit;
}
}
My question is how can i send post request,using header :
Header Username : EBA Token :
34242353453456563DSFS
This is so far the easiest method i have ever tried for "Basic Authentication".
Use the below code to generate the auth header (API/Repository class)
var basic = Credentials.basic("YOUR_USERNAME", "YOUR_PASSWORD")
Pass this as header to the webservice call (API/Repository class)
var retrofitCall = myWebservice.getNewsFeed(basic)
Add the basic header as parameter (Retrofit Webservice interface class)
#GET("newsfeed/daily")
fun getNewsFeed(#Header("Authorization") h1:String):Call<NewsFeedResponse>
Sorry, my code is in Kotlin, but can be easily translated to Java.
References: https://mobikul.com/basic-authentication-retrofit-android/
make header like this way..
private Retrofit getClient(final Context context) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(60, TimeUnit.SECONDS);
client.writeTimeout(60, TimeUnit.SECONDS);
client.connectTimeout(60, TimeUnit.SECONDS);
client.addInterceptor(interceptor);
client.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (context == null) {
request = request
.newBuilder()
.build();
} else {
request = request
.newBuilder()
.addHeader("Authorization", "Bearer " + AppSetting.getStringSharedPref(context, Constants.USER_KEY_TOKEN, ""))
.build();
}
return chain.proceed(request);
}
});
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit;
}
Use Header annotation
#FormUrlEncoded
#POST("GetBarcodeDetail")
Call<PreliminaryGoodsAcceptResponse> PRELIMINARY_GOODS_ACCEPT_RESPONSE_CALL(#Header("Authorization") token: String,#Field("ProcName") String procName, #Field("Barcode") String barcode, #Field("LangCode") String langCode);
Simple-Retrofit-API-request-and-Data-Loading Here I just add the project where create the API call to access data from database using retrofit library; which is leading library to access data on network. And display the accessed data in the List format. Create the Simple Android Studio Project with Empty Activity. Create the Adapter and activity item to show normal lists in android app. Now Create the App class extending Application, as Application class is a singleton that you can access from any activity or anywhere else you have a Context object.
You can check the more details about Application class from https://github.com/codepath/android_guides/wiki/Understanding-the-Android-Application-Class Why extend an Application class? https://developer.android.com/reference/android/app/Application.html
Add android:name=".YourApplication" i.e. class name extending the Application class in android. and class will be like public class YourApplication extends Application Init the Retrofit in Application class
//network code start
//init http logger
httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
// init client client = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(new Interceptor() {
#Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request request2 = request.newBuilder().build();
return chain.proceed(request2);
}
}).connectTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).build();
Gson gson = new GsonBuilder().setLenient().create();
Retrofit mRetrofit = new Retrofit.Builder().baseUrl(Constants.API_BASE_URL).client(client).addConverterFactory(GsonConverterFactory.create(gson)).build();
mWebservice = mRetrofit.create(Webservice.class);
While Constants.API_BASE_URL is base url Create the Webervice.class where you can call the API with parameters e.g. In case of GET Method:
#GET("webservices/GetAllClientsDemoRetro.php")
Call updateChatStatus();
In case of POST method:
#FormUrlEncoded
#Headers({"Content-Type: application/x-www-form-urlencoded"})
#POST("webservices/GetAllClientsDemoRetro.php")
Call updateChatStatus();
You can See the more in details About Retrofit on Official API declaration here: http://square.github.io/retrofit/
We can parse the values with POJO i.e. Setter and Getter, using the Parceble class. Since parsing key name should be equal to the value we are receiving from the JSON response. POJO class should be declared like public class ClientData implements Parcelable { then declare the keys in the class, key values means
public class ClientData implements Parcelable
{
public String client_id;
public String company_name;
public String address_line;
public String city;
public String pincode;
public String state;
public String country;
}
Now using Alt+Enter i.e. select the option Add Parceble Implementation and press enter. Then automatically parceble class will be added. Also you have to add Setter and Getter method in class using Alt + Insert. Note: Don’t add the Setter and Getter methods for CREATER: Creater<> method If you want to use different key that JSON response key, then you should use Serialization. When I was using same key then its is like public String client_id; But when I am using the Serialization, then I can use like #Serializattion(“client_id”) public String ClientID; Now last but not a list, We call the API using retrofit, and use the response to view the Item in list-
RetroFitApplication.getWebservice().updateChatStatus().enqueue(new Callback() {
#Override public void onResponse(Call call, Response response) {
Log.d("retrofilt success", "" + response.body());
if (response.body() != null) {
clientResponceData = response.body();
Gson gson = new Gson();
String body = gson.toJson(response.body());
Log.d("retrofilt success2", "clientData" + clientResponceData.getResponse());
if (clientResponceData.getResponse() != null) {
initRV();
}
} else {
// Empty Client List Toast.makeText(ClientList.this, "Empty List", Toast.LENGTH_SHORT).show();
}
}
#Override public void onFailure(Call call, Throwable t) {
Log.d("retrofilt error", "" + t);
Toast.makeText(ClientList.this, "No Internet Connection", Toast.LENGTH_SHORT).show();
}
});
By using the Construction in Adapter, we can use the values from the response. Guys I added this repository to get the Entire idea of calling the API and get the response from server using the Retrofit Library. I write this entire documents in details with simple word.

How can I handle empty response body with Retrofit 2?

Recently I started using Retrofit 2 and I faced an issue with parsing empty response body. I have a server which responds only with http code without any content inside the response body.
How can I handle only meta information about server response (headers, status code etc)?
Edit:
As Jake Wharton points out,
#GET("/path/to/get")
Call<Void> getMyData(/* your args here */);
is the best way to go versus my original response --
You can just return a ResponseBody, which will bypass parsing the response.
#GET("/path/to/get")
Call<ResponseBody> getMyData(/* your args here */);
Then in your call,
Call<ResponseBody> dataCall = myApi.getMyData();
dataCall.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Response<ResponseBody> response) {
// use response.code, response.headers, etc.
}
#Override
public void onFailure(Throwable t) {
// handle failure
}
});
If you use RxJava, then it's better to use Completable in this case
Represents a deferred computation without any value but only indication for completion or exception. The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)?
http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Completable.html
in the accepted answer:
#GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);
If the endpoint returns failure response code, it will still be in the onNext and you will have to check the response code yourself.
However, if you use Completable.
#GET("/path/to/get")
Completable getMyData(/* your args here */);
you will have only onComplete and onError.
if the response code is success it will fire the onComplete else it will fire onError.
If you are using rxjava, use something like :
#GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);
With kotlin, using the return type Call<Void> still throws IllegalArgumentException: Unable to create converter for retrofit2.Call<java.lang.Void>
Using Response instead of Call resolved the issue
#DELETE("user/data")
suspend fun deleteUserData(): Response<Void>
Here is an example Kotlin in MVVM with service, Repository and ViewModel:
Service:
#POST("/logout")
suspend fun logout(#Header("Authorization") token: String):Response<Unit>
Repository:
//logout
private val mLogoutResponse = MutableLiveData<String>()
val logoutResponse: LiveData<String>
get() {
return mLogoutResponse
}
suspend fun logout(token: String) {
try {
val result=quizzerProfileApi.logout(token)
if(result.code()!=0)
{
mLogoutResponse.postValue(result.code().toString())
}
} catch (e: Exception) {
Log.d("ProfileRepository", "logout: Error: $e")
}
}
ViewModel:
fun logout(token: String) {
viewModelScope.launch {
repository.logout(token)
}
}
val logoutResponseCd: LiveData<String>
get() = repository.logoutResponse
in Activity:
private fun logout() {
myViewModel.logout(token)
myViewModel.logoutResponseCd.observe(this, Observer {
if(it!="0"){
Log.d(TAG, "logout: code= $it")
finish()
}
else
Toast.makeText(this, "Error logging out: $it", Toast.LENGTH_SHORT).show()
})
}
Here is how I used it with Rx2 and Retrofit2, with PUT REST request:
My request had a json body but just http response code with empty body.
The Api client:
public class ApiClient {
public static final String TAG = ApiClient.class.getSimpleName();
private DevicesEndpoint apiEndpointInterface;
public DevicesEndpoint getApiService() {
Gson gson = new GsonBuilder()
.setLenient()
.create();
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpClientBuilder.addInterceptor(logging);
OkHttpClient okHttpClient = okHttpClientBuilder.build();
apiEndpointInterface = new Retrofit.Builder()
.baseUrl(ApiContract.DEVICES_REST_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(DevicesEndpoint.class);
return apiEndpointInterface;
}
The interface:
public interface DevicesEndpoint {
#Headers("Content-Type: application/json")
#PUT(ApiContract.DEVICES_ENDPOINT)
Observable<ResponseBody> sendDeviceDetails(#Body Device device);
}
Then to use it:
private void sendDeviceId(Device device){
ApiClient client = new ApiClient();
DevicesEndpoint apiService = client.getApiService();
Observable<ResponseBody> call = apiService.sendDeviceDetails(device);
Log.i(TAG, "sendDeviceId: about to send device ID");
call.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() {
#Override
public void onSubscribe(Disposable disposable) {
}
#Override
public void onNext(ResponseBody body) {
Log.i(TAG, "onNext");
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "onError: ", t);
}
#Override
public void onComplete() {
Log.i(TAG, "onCompleted: sent device ID done");
}
});
}
You can try this one
Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(new NullOnEmptyConverterFactory())
.client(okHttpClient).build();
class NullOnEmptyConverterFactory extends Converter.Factory {
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return (Converter<ResponseBody, Object>) body -> {
if (body.source().exhausted()) return null;
return delegate.convert(body);
};
}
}
Kotlin, Retrofit
#POST
suspend fun accountVerification(
#Body requestBody: RequestBody
): Response<Unit>
and success can be check using
if (response.isSuccessful) { }

Refreshing OAuth token using Retrofit without modifying all calls

We are using Retrofit in our Android app, to communicate with an OAuth2 secured server. Everything works great, we use the RequestInterceptor to include the access token with each call.
However there will be times, when the access token will expire, and the token needs to be refreshed. When the token expires, the next call will return with an Unauthorized HTTP code, so that's easy to monitor.
We could modify each Retrofit call the following way:
In the failure callback, check for the error code, if it equals Unauthorized, refresh the OAuth token, then repeat the Retrofit call.
However, for this, all calls should be modified, which is not an easily maintainable, and good solution.
Is there a way to do this without modifying all Retrofit calls?
Please do not use Interceptors to deal with authentication.
Currently, the best approach to handle authentication is to use the new Authenticator API, designed specifically for this purpose.
OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with them.
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();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
Attach an Authenticator to an OkHttpClient the same way you do with Interceptors
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Use this client when creating your Retrofit RestAdapter
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setClient(new OkClient(okHttpClient))
.build();
return restAdapter.create(API.class);
If you are using Retrofit >= 1.9.0 then you could make use of OkHttp's new Interceptor, which was introduced in OkHttp 2.2.0. You would want to use an Application Interceptor, which permits you to retry and make multiple calls.
Your Interceptor could look something like this pseudocode:
public class CustomInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
if (response shows expired token) {
// close previous response
response.close()
// get a new token (I use a synchronous Retrofit call)
// create a new request and modify it accordingly using the new token
Request newRequest = request.newBuilder()...build();
// retry the request
return chain.proceed(newRequest);
}
// otherwise just pass the original response on
return response;
}
}
After you define your Interceptor, create an OkHttpClient and add the interceptor as an Application Interceptor.
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new CustomInterceptor());
And finally, use this OkHttpClient when creating your RestAdapter.
RestService restService = new RestAdapter().Builder
...
.setClient(new OkClient(okHttpClient))
.create(RestService.class);
Warning: As Jesse Wilson (from Square) mentions here, this is a dangerous amount of power.
With that being said, I definitely think this is the best way to handle something like this now. If you have any questions please don't hesitate to ask in a comment.
TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I break this cycle? Two different OkHttpClients? They are going to have different connection pools..
If you have, say, a Retrofit TokenService that you need inside your Authenticator but you would only like to set up one OkHttpClient you can use a TokenServiceHolder as a dependency for TokenAuthenticator. You would have to maintain a reference to it at the application (singleton) level. This is easy if you are using Dagger 2, otherwise just create class field inside your Application.
In TokenAuthenticator.java
public class TokenAuthenticator implements Authenticator {
private final TokenServiceHolder tokenServiceHolder;
public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) {
this.tokenServiceHolder = tokenServiceHolder;
}
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
//is there a TokenService?
TokenService service = tokenServiceHolder.get();
if (service == null) {
//there is no way to answer the challenge
//so return null according to Retrofit's convention
return null;
}
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken().execute();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
In TokenServiceHolder.java:
public class TokenServiceHolder {
TokenService tokenService = null;
#Nullable
public TokenService get() {
return tokenService;
}
public void set(TokenService tokenService) {
this.tokenService = tokenService;
}
}
Client setup:
//obtain instance of TokenServiceHolder from application or singleton-scoped component, then
TokenAuthenticator authenticator = new TokenAuthenticator(tokenServiceHolder);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(tokenAuthenticator);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(okHttpClient)
.build();
TokenService tokenService = retrofit.create(TokenService.class);
tokenServiceHolder.set(tokenService);
If you are using Dagger 2 or a similar dependency injection framework there are some examples in the answers to this question
Using TokenAuthenticator like #theblang answer is a correct way for handle refresh_token.
Here is my implement (I have using Kotlin, Dagger, RX but you may use this idea for implement to your case)
TokenAuthenticator
class TokenAuthenticator #Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {
override fun authenticate(route: Route, response: Response): Request? {
val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
return response.request().newBuilder()
.header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
.build()
}
}
For prevent dependency cycle like #Brais Gabin comment, I create 2 interface like
interface PotoNoneAuthApi { // NONE authentication API
#POST("/login")
fun login(#Body request: LoginRequest): Single<AccessToken>
#POST("refresh_token")
#FormUrlEncoded
fun refreshToken(#Field("refresh_token") refreshToken: String): Single<AccessToken>
}
and
interface PotoAuthApi { // Authentication API
#GET("api/images")
fun getImage(): Single<GetImageResponse>
}
AccessTokenWrapper class
class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
private var accessToken: AccessToken? = null
// get accessToken from cache or from SharePreference
fun getAccessToken(): AccessToken? {
if (accessToken == null) {
accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
}
return accessToken
}
// save accessToken to SharePreference
fun saveAccessToken(accessToken: AccessToken) {
this.accessToken = accessToken
sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
}
}
AccessToken class
data class AccessToken(
#Expose
var token: String,
#Expose
var refreshToken: String)
My Interceptor
class AuthInterceptor #Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val authorisedRequestBuilder = originalRequest.newBuilder()
.addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
.header("Accept", "application/json")
return chain.proceed(authorisedRequestBuilder.build())
}
}
Finally, add Interceptor and Authenticator to your OKHttpClient when create service PotoAuthApi
Demo
https://github.com/PhanVanLinh/AndroidMVPKotlin
Note
Authenticator flow
Example API getImage() return 401 error code
authenticate method inside TokenAuthenticator will fired
Synchronize noneAuthAPI.refreshToken(...) called
After noneAuthAPI.refreshToken(...) response -> new token will add to header
getImage() will AUTO called with new header (HttpLogging WILL NOT log this call) (intercept inside AuthInterceptor WILL NOT CALLED)
If getImage() still failed with error 401, authenticate method inside TokenAuthenticator will fired AGAIN and AGAIN then it will throw error about call method many time(java.net.ProtocolException: Too many follow-up requests). You can prevent it by count response. Example, if you return null in authenticate after 3 times retry, getImage() will finish and return response 401
If getImage() response success => we will result the result normally (like you call getImage() with no error)
Hope it help
As Brais Gabin said in the comment I had the problem that TokenAuthenticator depends on a service class. The service class depends on an OkHttpClient instance and to create an OkHttpClient I need the TokenAuthenticator.
So how I broke this cycle?
I created a new okHttpClient object, a new Retrofit object and with that object I did the call to get the new token using the refreshToken ( check getUpdatedToken() function)
class TokenAuthenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
// 1. Refresh your access_token using a synchronous api request
val response = getUpdatedToken(refreshToken)
//2. In my case here I store the new token and refreshToken into SharedPreferences
response.request.newBuilder()
.header("Authorization", "Bearer ${tokenResponse.data?.accessToken}")
.build()
// 3. If there's any kind of error I return null
}
}
private suspend fun getUpdatedToken( refreshToken: String): TokenResponse {
val okHttpClient = OkHttpClient().newBuilder()
.addInterceptor(errorResponseInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
val service = retrofit.create(RefreshTokenApi::class.java)
return service.refreshToken(refreshToken)
}
}
RefreshTokenApi
interface RefreshTokenApi {
#FormUrlEncoded
#POST("refreshToken")
suspend fun refreshToken(
#Field("refresh_token") refreshToeken: String
): TokenResponse
}
In this project I'm using Koin and I configured this way:
object RetrofigConfig {
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
fun provideOkHttpClient(
tokenAuthenticator: TokenAuthenticator
): OkHttpClient {
return OkHttpClient().newBuilder()
.authenticator(tokenAuthenticator)
.build()
}
fun provideServiceApi(retrofit: Retrofit): ServiceApi {
return retrofit.create(ServiceApi::class.java)
}
}
The important line there is OkHttpClient().newBuilder().authenticator(tokenAuthenticator)
Because this is the first time I'm implementing this I don't know if this is the best way but is the way it is working in my project.
I know this an old thread, but just in case someone stumbled in it.
TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I break this cycle? Two different OkHttpClients? They are going to have different connection pools..
I was facing the same problem, but I wanted to create only one OkHttpClient becuase I don't think that I need another one for just the TokenAuthenticator itself, I was using Dagger2, so I ended up providing the service class as Lazy injected in the TokenAuthenticator, you can read more about Lazy injection in dagger 2 here, but it's like basically saying to Dagger to NOT go and create the service needed by the TokenAuthenticator right away.
You can refer to this SO thread for sample code: How to resolve a circular dependency while still using Dagger2?
Using one Interceptor (inject the token) and one Authenticator (refresh operations) do the job but:
I had a double call problem too: the first call always returned a 401:
the token wasn't inject at the first call (interceptor) and the authenticator was called: two requests were made.
The fix was just to reaffect the request to the build in the Interceptor:
BEFORE:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request();
//...
request.newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
AFTER:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request();
//...
request = request.newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
IN ONE BLOCK:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request().newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
Hope it helps.
Edit: I didn't find a way to avoid the first call to always returning 401 using only the authenticator and no interceptor
You can try creating a base class for all your loaders in which you would be able to catch a particular exception and then act as you need.
Make all your different loaders extend from the base class in order to spread the behaviour.
After Long research, I customized Apache client to handle Refreshing AccessToken For Retrofit In which you send access token as parameter.
Initiate your Adapter with cookie Persistent Client
restAdapter = new RestAdapter.Builder()
.setEndpoint(SERVER_END_POINT)
.setClient(new CookiePersistingClient())
.setLogLevel(RestAdapter.LogLevel.FULL).build();
Cookie Persistent client which maintains cookies for all requests and checks with each request response, if it is unauthorized access ERROR_CODE = 401, refresh access token and recall the request, else just processes request.
private static class CookiePersistingClient extends ApacheClient {
private static final int HTTPS_PORT = 443;
private static final int SOCKET_TIMEOUT = 300000;
private static final int CONNECTION_TIMEOUT = 300000;
public CookiePersistingClient() {
super(createDefaultClient());
}
private static HttpClient createDefaultClient() {
// Registering https clients.
SSLSocketFactory sf = null;
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
trustStore.load(null, null);
sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params,
CONNECTION_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", sf, HTTPS_PORT));
// More customization (https / timeouts etc) can go here...
ClientConnectionManager cm = new ThreadSafeClientConnManager(
params, registry);
DefaultHttpClient client = new DefaultHttpClient(cm, params);
// Set the default cookie store
client.setCookieStore(COOKIE_STORE);
return client;
}
#Override
protected HttpResponse execute(final HttpClient client,
final HttpUriRequest request) throws IOException {
// Set the http context's cookie storage
BasicHttpContext mHttpContext = new BasicHttpContext();
mHttpContext.setAttribute(ClientContext.COOKIE_STORE, COOKIE_STORE);
return client.execute(request, mHttpContext);
}
#Override
public Response execute(final Request request) throws IOException {
Response response = super.execute(request);
if (response.getStatus() == 401) {
// Retrofit Callback to handle AccessToken
Callback<AccessTockenResponse> accessTokenCallback = new Callback<AccessTockenResponse>() {
#SuppressWarnings("deprecation")
#Override
public void success(
AccessTockenResponse loginEntityResponse,
Response response) {
try {
String accessToken = loginEntityResponse
.getAccessToken();
TypedOutput body = request.getBody();
ByteArrayOutputStream byte1 = new ByteArrayOutputStream();
body.writeTo(byte1);
String s = byte1.toString();
FormUrlEncodedTypedOutput output = new FormUrlEncodedTypedOutput();
String[] pairs = s.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
if (URLDecoder.decode(pair.substring(0, idx))
.equals("access_token")) {
output.addField("access_token",
accessToken);
} else {
output.addField(URLDecoder.decode(
pair.substring(0, idx), "UTF-8"),
URLDecoder.decode(
pair.substring(idx + 1),
"UTF-8"));
}
}
execute(new Request(request.getMethod(),
request.getUrl(), request.getHeaders(),
output));
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void failure(RetrofitError error) {
// Handle Error while refreshing access_token
}
};
// Call Your retrofit method to refresh ACCESS_TOKEN
refreshAccessToken(GRANT_REFRESH,CLIENT_ID, CLIENT_SECRET_KEY,accessToken, accessTokenCallback);
}
return response;
}
}
here is my code working for me. may be helpful for some one
class AuthenticationInterceptorRefreshToken #Inject
constructor( var hIltModules: HIltModules,) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val response = chain.proceed(originalRequest)
if (response.code == 401) {
synchronized(this) {
val originalRequest = chain.request()
val authenticationRequest = originalRequest.newBuilder()
.addHeader("refreshtoken", " $refreshToken")
.build()
val initialResponse = chain.proceed(authenticationRequest)
when (initialResponse.code) {
401 -> {
val responseNewTokenLoginModel = runBlocking {
hIltModules.provideAPIService().refreshToken()
}
when (responseNewTokenLoginModel.statusCode) {
200 -> {
refreshToken = responseNewTokenLoginModel.refreshToken
access_token = responseNewTokenLoginModel.accessToken
val newAuthenticationRequest = originalRequest.newBuilder()
.header("refreshtoken",
" $refreshToken")
.build()
return chain.proceed(newAuthenticationRequest)
}
else -> {
return null!!
}
}
}
else -> return initialResponse
}
}
}; return response
}
To anyone who wanted to solve concurrent/parallel calls when refreshing token. Here's a workaround
class TokenAuthenticator: Authenticator {
override fun authenticate(route: Route?, response: Response?): Request? {
response?.let {
if (response.code() == 401) {
while (true) {
if (!isRefreshing) {
val requestToken = response.request().header(AuthorisationInterceptor.AUTHORISATION)
val currentToken = OkHttpUtil.headerBuilder(UserService.instance.token)
currentToken?.let {
if (requestToken != currentToken) {
return generateRequest(response, currentToken)
}
}
val token = refreshToken()
token?.let {
return generateRequest(response, token)
}
}
}
}
}
return null
}
private fun generateRequest(response: Response, token: String): Request? {
return response.request().newBuilder()
.header(AuthorisationInterceptor.USER_AGENT, OkHttpUtil.UA)
.header(AuthorisationInterceptor.AUTHORISATION, token)
.build()
}
private fun refreshToken(): String? {
synchronized(TokenAuthenticator::class.java) {
UserService.instance.token?.let {
isRefreshing = true
val call = ApiHelper.refreshToken()
val token = call.execute().body()
UserService.instance.setToken(token, false)
isRefreshing = false
return OkHttpUtil.headerBuilder(token)
}
}
return null
}
companion object {
var isRefreshing = false
}
}

Categories

Resources