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) { }
Related
I want to design API calls in such a way that it will be easy to handle success and failure responses easily from one place (instead of writing same code of call function for all APIs)
Here are the scenarios which I want to consider.
Handle success / failure and error responses like 4xx, 5xx etc of all APIs at one central place.
Want to cancel enqueue requests and also stop processing response if request is already sent in case of logout (because response parsing will modify some global data of app)
If access token has expired and 401 response received from cloud, it should get new token and then call API again automatically with new token.
My current implementation is not satisfying above requirements.
Is there any way to implement API calls which satisfy above requirements using Retrofit ?
Please suggest me a good design for this.
Here is my current implementation :
ApiInterface.java - It is an interface which contains different API calls definitions.
ApiClient.java - To get retrofit client object to call APIs.
ApiManager.java - It has methods to call APIs and parse their responses.
ApiInterface.java
public interface ApiInterface {
// Get Devices
#GET("https://example-base-url.com" + "/devices")
Call<ResponseBody> getDevices(#Header("Authorization) String token);
// Other APIs......
}
ApiClient.java
public class ApiClient {
private static Retrofit retrofitClient = null;
static Retrofit getClient(Context context) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), systemDefaultTrustManager())
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
retrofitClient = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
}
ApiManager.java
public class ApiManager {
private static ApiManager apiManager;
public static ApiManager getInstance(Context context) {
if (apiManager == null) {
apiManager = new ApiManager(context);
}
return apiManager;
}
private ApiManager(Context context) {
this.context = context;
apiInterface = ApiClient.getClient(context).create(ApiInterface.class);
}
public void getDevices(ResponseListener listener) {
// API call and response handling
}
// Other API implementation
}
Update :
For 1st point, interceptor will be helpful to handle 4xx, 5xx responses globally according to this.
But interceptor will be in the ApiClient file and to inform UI or API caller component, need to pass success or failure result in callback I mean response listener.
How can I do that ? Any idea ?
For 3rd point, I know little bit about Retrofit Authenticator. I think for this point it is suitable but it requires synchronous call to get new token using refresh token.
How can I make asynchronous call to synchronous ? (Note : this call is not retrofit call)
By handling the success/failure responses at a central place I'll assume you want to get rid of repeated boilerplate based on the error parsing logic and how it may create UI side-effects for your app.
I'd probably suggest keeping things really simple by creating a custom abstraction for Callback which invokes your APIs for success/failure according to your domain logic.
Here's something fairly simple implementation for use-case (1) :
abstract class CustomCallback<T> implements Callback<T> {
abstract void onSuccess(T response);
abstract void onFailure(Throwable throwable);
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
onSuccess(response.body());
} else {
onFailure(new HttpException(response));
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
onFailure(t);
}
}
For use-case (2), to be able to cancel all enqueued calls upon a global event like logout you'd have to keep a reference to all such objects. Fortunately, Retrofit supports plugging in a custom call factory okhttp3.Call.Factory
You could use your implementation as a singleton to hold a collection of calls and in the event of a logout notify it to cancel all requests in-flight. Word of caution, do use weak references of such calls in the collection to avoid leaks/references to dead calls. (also you might want to brainstorm on the right collection to use or a periodic cleanup of weak references based on the transactions)
For use-case (3), Authenticator should work out fine since you've already figured out the usage there are 2 options -
Migrate the refresh token call to OkHttp/Retrofit and fire it synchronously
Use a count-down latch to make the authenticator wait for the async call to finish (with a timeout set to connection/read/write timeout for the refresh token API call)
Here's a sample implementation:
abstract class NetworkAuthenticator implements Authenticator {
private final SessionRepository sessionRepository;
public NetworkAuthenticator(SessionRepository repository) {
this.sessionRepository = repository;
}
public Request authenticate(#Nullable Route route, #NonNull Response response) {
String latestToken = getLatestToken(response);
// Refresh token failed, trigger a logout for the user
if (latestToken == null) {
logout();
return null;
}
return response
.request()
.newBuilder()
.header("AUTHORIZATION", latestToken)
.build();
}
private synchronized String getLatestToken(Response response) {
String currentToken = sessionRepository.getAccessToken();
// For a signed out user latest token would be empty
if (currentToken.isEmpty()) return null;
// If other calls received a 401 and landed here, pass them through with updated token
if (!getAuthToken(response.request()).equals(currentToken)) {
return currentToken;
} else {
return refreshToken();
}
}
private String getAuthToken(Request request) {
return request.header("AUTHORIZATION");
}
#Nullable
private String refreshToken() {
String result = null;
CountDownLatch countDownLatch = new CountDownLatch(1);
// Make async call to fetch token and update result in the callback
// Wait up to 10 seconds for the refresh token to succeed
try {
countDownLatch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
abstract void logout();
}
I hope this helps with your network layer implementation
So, With the help of official sample in the retrofit github repository here: https://github.com/square/retrofit/blob/fbf1225e28e2094bec35f587b8933748b705d167/samples/src/main/java/com/example/retrofit/ErrorHandlingAdapter.java
The ErrorHandlingAdapter is the closest you can get to your requirement because it lets you control enqueuing of the call, creating the error callbacks, calling error callbacks on your own. Whether you want the caller to do some action or you want to handle it yourself in one place or just both.
So this is how you can create it. Do read the inline comments to understand.
public final class ErrorHandlingAdapter {
/**
* Here you'll decide how many methods you want the caller to have.
*/
interface MyCallback<T> {
void success(Response<T> response);
void error(String s);
}
/**
* This is your call type
*/
interface MyCall<T> {
void cancel();
void enqueue(MyCallback<T> callback);
#NotNull
MyCall<T> clone();
}
public static class ErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
#Override
public #Nullable
CallAdapter<?, ?> get(
#NotNull Type returnType, #NotNull Annotation[] annotations, #NotNull Retrofit retrofit) {
if (getRawType(returnType) != MyCall.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalStateException(
"MyCall must have generic type (e.g., MyCall<ResponseBody>)");
}
Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
Executor callbackExecutor = retrofit.callbackExecutor();
return new ErrorHandlingCallAdapter<>(responseType, callbackExecutor);
}
private static final class ErrorHandlingCallAdapter<R> implements CallAdapter<R, MyCall<R>> {
private final Type responseType;
private final Executor callbackExecutor;
ErrorHandlingCallAdapter(Type responseType, Executor callbackExecutor) {
this.responseType = responseType;
this.callbackExecutor = callbackExecutor;
}
#NotNull
#Override
public Type responseType() {
return responseType;
}
#Override
public MyCall<R> adapt(#NotNull Call<R> call) {
return new MyCallAdapter<>(call, callbackExecutor);
}
}
}
static class MyCallAdapter<T> implements MyCall<T> {
private final Call<T> call;
private final Executor callbackExecutor;
MyCallAdapter(Call<T> call, Executor callbackExecutor) {
this.call = call;
this.callbackExecutor = callbackExecutor;
}
#Override
public void cancel() {
call.cancel();
}
#Override
public void enqueue(final MyCallback<T> callback) {
if (!SomeCondition.myCondition) {
// Don't enqueue the call if my condition doesn't satisfy
// it could be a flag in preferences like user isn't logged in or
// some static flag where you don't want to allow calls
return;
}
call.clone().enqueue(
new Callback<T>() {
#Override
public void onResponse(#NotNull Call<T> call, #NotNull Response<T> response) {
callbackExecutor.execute(() -> {
int code = response.code();
if (code >= 200 && code < 300) {
//success response
callback.success(response);
} else if (code == 401) {
// Unauthenticated so fetch the token again
getTheTokenAgain(callback);
} else if (code >= 400 && code < 500) {
//handle error the way you want
callback.error("Client error");
} else if (code >= 500 && code < 600) {
//handle error the way you want
callback.error("Server error");
} else {
//handle error the way you want
callback.error("Something went wrong");
}
});
}
#Override
public void onFailure(#NotNull Call<T> call, #NotNull Throwable t) {
callbackExecutor.execute(() -> {
if (t instanceof IOException) {
callback.error("IOException");
} else {
callback.error("Some exception");
}
});
}
});
}
private void getTheTokenAgain(MyCallback<T> callback) {
// Make the call to get the token & when token arrives enqueue it again
// Don't forget to put termination condition like 3 times, if still not successful
// then just log user out or show error
// This is just dummy callback, you'll need to make a
// call to fetch token
new MyTokenCallback() {
#Override
public void onTokenArrived(String token) {
//enqueue(callback); here
}
#Override
public void onTokenFetchFailed() {
callbackExecutor.execute(() -> {
callback.error("Counld't fetch token");
});
}
};
// This is for demo you should put it in success callback
SomeCondition.callCount++;
Log.d("MG-getTheTokenAgain", "Method called");
if (SomeCondition.callCount < 3) {
enqueue(callback);
} else {
callbackExecutor.execute(() -> {
callback.error("Counld't fetch token");
});
}
}
#NotNull
#Override
public MyCall<T> clone() {
return new MyCallAdapter<>(call.clone(), callbackExecutor);
}
}
}
This is how you'll plug this adapter:
private void makeApiCall() {
//This is just for demo to generate 401 error you won't need this
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(chain -> {
Request request = chain.request().newBuilder()
.addHeader("Accept","application/json")
.addHeader("Authorization", "cdsc").build();
return chain.proceed(request);
});
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl("http://httpbin.org/")
.addCallAdapterFactory(new ErrorHandlingAdapter.ErrorHandlingCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
HttpBinService service = retrofit.create(HttpBinService.class);
ErrorHandlingAdapter.MyCall<Ip> ip = service.getIp();
ip.enqueue(
new ErrorHandlingAdapter.MyCallback<Ip>() {
#Override
public void success(Response<Ip> response) {
Log.d("MG-success", response.toString());
}
#Override
public void error(String s) {
Log.d("MG-error", s);
}
});
}
You might have to bend some things to your needs, but I think this could be good reference because it's in the official sample.
1. Handle success / failure and error responses like 4xx, 5xx etc of
all APIs at one central place.
Create following two classes:
ApiResponse.kt
class ApiResponse<T : Any> {
var status: Boolean = true
var message: String = ""
var data: T? = null
}
ApiCallback.kt
abstract class ApiCallback<T : Any> : Callback<ApiResponse<T>> {
abstract fun onSuccess(response: ApiResponse<T>)
abstract fun onFailure(response: ApiResponse<T>)
override fun onResponse(call: Call<ApiResponse<T>>, response: Response<ApiResponse<T>>) {
if (response.isSuccessful && response.body() != null && response.code() == 200) {
onSuccess(response.body()!!)
} else { // handle 4xx & 5xx error codes here
val resp = ApiResponse<T>()
resp.status = false
resp.message = response.message()
onFailure(resp)
}
}
override fun onFailure(call: Call<ApiResponse<T>>, t: Throwable) {
val response = ApiResponse<T>()
response.status = false
response.message = t.message.toString()
onFailure(response)
}
}
Now use the above ApiCallback class instead of Retrofit's Callback class to enqueue
2. Want to cancel enqueue requests and also stop processing response if request is already sent in case of logout (because response parsing will modify some global data of app)
You cannot stop processing a response midway, but what you can do is not update the ui or activity if the activity in question is not in foreground, which can be done with the help of LiveData from the MVVM Architecture.
3. If access token has expired and 401 response received from cloud, it should get new token and then call API again automatically with new token.
Create a TokenAuthenticator.java class like this
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 instance of the above authenticator to OkHttpClient like this
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Then finally, attach okHttpClient to the Retrofit instance as you have already done
More info regarding the authenticator part can be found in this answer here
I'm working on an application that uses Retrofit for network operations. As it stands, everything works well with GsonConverterFactory handling serialization. Here is how I setup Retrofit
Retrofit.Builder()
.baseUrl("<base url>")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
Now I need to connect to a legacy service which returns content in text/plain; charset=utf-8 format. Here is the Retrofit interface
#GET("https://<domain>/<endpoint>?Type=Query")
suspend fun callStatus(#Query("userId") id: Int): Response<String>
This will return status of a call for a valid user. For instance, if the user is valid and there is a status, it returns "Active" as plain text. If there is no valid user, it returns an error code of #1005
I could add custom converter factory like this (found on the web)
final class StringConverterFactory implements Converter.Factory {
private StringConverterFactory() {}
public static StringConverterFactory create() {
return new StringConverterFactory();
}
#Override
public Converter<String> get(Type type) {
Class<?> cls = (Class<?>) type;
if (String.class.isAssignableFrom(cls)) {
return new StringConverter();
}
return null;
}
private static class StringConverter implements Converter<String> {
private static final MediaType PLAIN_TEXT = MediaType.parse("text/plain; charset=UTF-8");
#Override
public String fromBody(ResponseBody body) throws IOException {
return new String(body.bytes());
}
#Override
public RequestBody toBody(String value) {
return RequestBody.create(PLAIN_TEXT, convertToBytes(value));
}
private static byte[] convertToBytes(String string) {
try {
return string.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
}
But I didn't see it make any difference. Also, it could well disguise JSON as normal text and break all existing service. Is there a better way to handle this scenario? I thought of having separate retrofit instance for plain text, bit dirty though. Do you have any other suggestions/solutions?
Edited
Response header contains the content type as
Content-Type: text/plain; charset=utf-8
Actual response for valid user
Active
Actual response for invalid user
#1005
Update
The order in which you register the converter factories matters. ScalarsConverterFactory must come first.
it should be possible by adding ScalarsConverterFactory when building the Retrofit object.
This can be done alongside with other json converters, e.g.
Retrofit.Builder()
.baseUrl("<base url>")
.client(client)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
After that, you should be able to receive plaintext responses.
You probably need to add this to your dependencies as well:
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
The following is the way that how I get response as plain text (using Java not Kotlin).
Step One
in your gradle (Module);
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
Step Two
Create an interface
public interface MyInterface {
#GET("something.php")
Call<String> getData(#Query("id") String id,
#Query("name") String name);
}
Step Three
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://example.com")
.addConverterFactory(ScalarsConverterFactory.create())
.build();
MyInterface myInterface = retrofit.create(MyInterface.class);
Call<String> call = myInterface.getData("id","myname");
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
String plain_text_response = response.body();
}
#Override
public void onFailure(Call<String> call, Throwable t) {
}
});
You don't need to use a your custom implementation of Converter.Factory you could just use
// your coroutine context
val response = callStatus(userId)
if(response.isSuccessful){
val plainTextContent = response.body()
// handle plainText
} else {
//TODO: Handle error
}
//...
Two things to check first that function should not be suspended & your response should be in the Callback
No need to add extra implementation of scalars.
#GET
fun getJson(
#Url baseUrl: String = slab_pro
): Call<DataClass>
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);
}
}
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();
}
});
I'm trying to solve a problem where I'll be making a couple of asynchronous calls and based on the original request, I'm performing a task. To solve this issue, I'm trying to add a TAG to each request and then on successful response, I can get the tag and take action based on the tag. Here, I'm using TAG only to identify the original request.
Problem
Before calling the enqueue method, I'm setting the tag to the original request. But when I get the response in the successful callback, I'm getting different tag that I didn't set. Somehow the request object itself is coming as the tag object there. I'm not sure, how???
Please check the code below-
GitHubService gitHubService = GitHubService.retrofit.create(GitHubService.class);
final Call<List<Contributor>> call = gitHubService.repoContributors("square", "retrofit");
// Set the string tag to the original request object.
call.request().newBuilder().tag("hello").build();
call.enqueue(new Callback<List<Contributor>>() {
#Override
public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
Log.d("tag", response.raw().request().tag().toString());
// I'm getting Request{method=GET, url=https://api.github.com/repos/square/retrofit/contributors, tag=null} as the value of the tag. WHY????
final TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(response.body().toString());
}
#Override
public void onFailure(Call<List<Contributor>> call, Throwable t) {
final TextView textView = (TextView) findViewById(R.id.textView);
textView.setText("Something went wrong: " + t.getMessage());
}
});
Can somebody point out that what exactly I'm doing wrong here. Any help would be appreciated.
For me this code is working
val CLIENT: OkHttpClient = OkHttpClient.Builder().apply {
addInterceptor(TagInterceptor())
}.build()
val SERVER_API: ServerApi = Retrofit.Builder()
.client(CLIENT)
.baseUrl(BASE_URL)
.build()
.create(ServerApi::class.java)
interface ServerApi {
#GET("api/notifications")
#Tag("notifications")
suspend fun getNotifications(): ResponseBody
}
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
annotation class Tag(val value: String)
internal class TagInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val builder = request.newBuilder()
request.tag(Invocation::class.java)?.let {
it.method().getAnnotation(Tag::class.java)?.let { tag ->
builder.tag(tag.value)
}
}
return chain.proceed(builder.build())
}
}
Then cancel by tag
fun OkHttpClient.cancelAll(tag: String) {
for (call in dispatcher().queuedCalls()) {
if (tag == call.request().tag()) {
call.cancel()
}
}
for (call in dispatcher().runningCalls()) {
if (tag == call.request().tag()) {
call.cancel()
}
}
}
CLIENT.cancelAll("notifications")
This solution is clearly a hack, but it works.
Let's say you create your Retrofit service like this :
public <S> S createService(Class<S> serviceClass) {
// Could be a simple "new"
Retrofit.Builder retrofitBuilder = getRetrofitBuilder(baseUrl);
// Could be a simple "new"
OkHttpClient.Builder httpClientBuilder = getOkHttpClientBuilder();
// Build your OkHttp client
OkHttpClient httpClient = httpClientBuilder.build();
Retrofit retrofit = retrofitBuilder.client(httpClient).build();
return retrofit.create(serviceClass);
}
You will need to add a new CallFactory to your Retrofit instance, so it adds a tag every-time. Since the tag will be read-only, we will use an array of Object containing only one element, which you will be able to change later on.
Retrofit retrofit = retrofitBuilder.client(httpClient).callFactory(new Call.Factory() {
#Override
public Call newCall(Request request) {
request = request.newBuilder().tag(new Object[]{null}).build();
Call call = httpClient.newCall(request);
// We set the element to the call, to (at least) keep some consistency
// If you want to only have Strings, create a String array and put the default value to null;
((Object[])request.tag())[0] = call;
return call;
}
}).build();
Now, after creating your call, you will be able to change the contents of your tag:
((Object[])call.request().tag())[0] = "hello";
The request already have tag on it . You can get it form this codeļ¼
val invocation: Invocation? = call.request().tag(Invocation::class.java)
if (invocation != null) {
Timber.d("tag--${invocation.method().name}}-------${invocation.arguments()}")
}