I can not understand the retrofit interceptors ,
private val OkHttpClient by lazy {
okhttp3.OkHttpClient.Builder()
.addInterceptor {
onOnIntercept(it)
}
.addInterceptor(LoggingInterceptor())
.addInterceptor(getInterceptor404())
.callTimeout(10, TimeUnit.MILLISECONDS)
// .addInterceptor(TimeoutInterceptor())
.build()
}
and what do these lines do, and If I have, multiple does the speed down?
val response: Response = chain.proceed(chain.request())
return chain.proceed(chain.request())
In Android sometimes you need to add a couple of parameters, like headers, to make a successful request, this is normal behavior from all the Android Apps when you are using Retrofit, you can do it in multiple ways
For example, you can add parameters directly to your request interface using the annotation Headers and putting a plain String, like this:
#Headers("Content-Type:application/json; charset=UTF-8")
#GET("yourwebsite/{someParam}/login")
fun logout(#Path("someParam") someParam: String?): Observable<LoginResponseViewModel>
Another solution is to send the Headers as a parameter to your interface function, using an annotation Header and sending a parameter, this gives you the possibility to have a custom parameter that you can manage from every request:
#Headers("Content-Type:application/json; charset=UTF-8")
#GET("yourwebsite/{someParam}/login")
fun logout(#Header(UUID.randomUUID().toString()) authToken: String?, #Path("someParam") someParam: String?): Observable<LoginResponseViewModel>**
Interceptor
A couple of people using Dagger probably will go for an Interceptor, you can have two types of interceptor:
The first one is using an interceptor directly in your Singleton, this will not give you versatility, but it will solve your problem faster, in this example, you can go for the chain object, get the request of the Retrofit call, get a new Builder and then add the Headers.
#Provides
#Singleton
fun getUnsafeOkHttpClient(): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val builder = OkHttpClient.Builder()
builder.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor { chain ->
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", UUID.randomUUID().toString())
.build()
chain.proceed(newRequest)
}
}
Yes, you can use multiple interceptors. When you do a request calling interface method using retrofit, your request go to the interceptor and then continue. In the interceptor you can rewrite or retry request. For example, you could add the access token in all request and refresh the token if is necessary, add the headers, another bodies, etc. When you received a response from api, the interceptor intercept the response too. But please, read the documentation to understand how it works. Have a nice coding!
Related
I have my Retrofit and OkHttp instances injected. I am changing the headers of my OkHttp object using an Interceptor. In this Interceptor, I set information such as app name, version etc.
I am now adding a login and want to set an authToken to the headers. However, this only exists AFTER login, obviously. So what I want to do is be able to add a new header after login for authToken. However, I can't get access to my OkHttp object as its buried in my module, and I just get my RestClient object injected in my class, with everything else built.
I was thinking of copying all my provides... code and making duplicates for before an after Login. For example, wrap my RestClient in a class called "PreLoginApi" that doesn't have authToken, and then creating another wrapper class called "PostLoginApi" which has the authToken, but this won't work if Dagger creates the objects on app launch.
#Provides
#Singleton
#Named("HeaderInterceptor")
fun provideHeaderInterceptor(context: Context): Interceptor {
val headerMap = context.getHeaderMap()
return Interceptor { chain ->
val original = chain.request()
val builder = original.newBuilder()
headerMap.keys.forEach {
builder.addHeader(it, headerMap[it] ?: "")
}
val request = builder.build()
chain.proceed(request)
}
}
#Provides
fun providesOkHttpClient(
#Named("HeaderInterceptor") headerInterceptor: Interceptor,
): OkHttpClient {
val builder = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.addInterceptor(headerInterceptor)
return builder.build()
}
You can see here where I call my extension function Context.getHeaderMap(). Inside this function, I'm retrieving the authToken from EncryptedSharedPreferences.
So ideally, I'd like to be able to tell Dagger to redo this initialisation once I login, because at this stage the authToken will be set. Is it possible to just re-initialise this one module or will I have to wrap my RestClient in another class, and maybe use Lazy loading to initialise the "PostLoginApi"?
Thanks.
EDIT:
I do similar for Firebase FCM token, where I pass in the headerMap during initialisation, like this:
private fun updateFirebaseToken(headerMap: HashMap<String, String>) {
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
return#OnCompleteListener
}
// Get new FCM registration token
task.result?.let { token ->
headerMap["FirebaseId"] = token
}
})
}
But I don't think I can do something like this for a SharedPrefs value.
Is there a way to add a header to a Retrofit object after it has been created ?
I create a Retrofit object using the Retrofit Builder and then at a later point need to add a certain header to it. The reason for adding it here is that this particular header needs to be added with all requests and its value is dynamic. I would like to avoid having to add this header to every network call separately. Here is how I create it:
Retrofit.Builder builder = new Retrofit.Builder()
.client(client)
.baseUrl(String.format(baseUrl, environmentExtension) + "/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
builder.build();
I would like to add the header to this existing object.
To add header to all the requests, you can intercept calls using Interceptor and tweak the request to add header. This has to be done while building OkHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(chain -> chain.proceed(chain.request().newBuilder()
.addHeader("key","value").build()));
OkHttpClient client = builder.build();//use this client in retrofit
The best way could be creating an Interceptor and adding it through the OkHttpClient's builder. You can achieve it as following;
class HeaderInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("Key", "Value")
val request = requestBuilder.build()
return chain.proceed(request)
}
}
val httpClient = OkHttpClient.Builder().apply {
connectTimeout(60, SECONDS)
readTimeout(60, SECONDS)
writeTimeout(60, SECONDS)
interceptors().add(headerInterceptor)
}.build()
You can introduce above interceptor to Retrofit's object as following;
Retrofit.Builder().apply {
baseUrl(BuildConfig.BASE_URL)
addConverterFactory(Json.asConverterFactory(MEDIA_TYPE_DEFAULT.toMediaType()))
client(httpClient)
}.build()
I am using retrofit request:
#Headers("Authorization: Basic: UsernamePassword")
#POST("services/2/something/something")
fun createPlan(#Body planData: PlanInfo): Call<Plan>
In my header I requred to send usrname and password and string in Base64 encode. But it not so hard to decode if decomiling the app.
So I refered to manual: https://medium.com/novumlogic/hiding-sensitive-data-in-android-app-dbd64e88224f for hiding sensitive data in android app.
But now when I use data from constant like so:
#Headers("Authorization: "+ConstantsEncrypted.usernamePassword())
#POST("services/2/something/something")
fun createPlan(#Body planData: PlanInfo): Call<Plan>
I get an error: An annotation argument must be a compile-time constant.
So how can I hide this sensitive data properly?
You cannot do that using the annotation method. Here's how I have done it in my projects.
class Authenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return response.request().newBuilder()
.header("Authorization", ConstantsEncrypted.usernamePassword()).build()
}
}
And don't forget to add it to your OkHttpClient.Builder() instance using authenticator(Authenticator()) something like below:
OkHttpClient.Builder().apply {
writeTimeout(120, TimeUnit.SECONDS)
readTimeout(120, TimeUnit.SECONDS)
connectTimeout(120, TimeUnit.SECONDS)
callTimeout(120, TimeUnit.SECONDS)
// your code
// this is where you add your authenticator
authenticator(Authenticator())
// more code
}.build()
The Authenticator is from package okhttp3. You can find more info on class here.
Found a solution, sending header to the function:
fun createPlan(#HeaderMap headers: Map<String, String>,#Body planData: PlanInfo): Call<Plan>
In iOS development, when I fetch an URL that displays XML, I can parse the whole XML file and use its data in my code, but in Kotlin I tried fetching the same URL and it returns only the first XML tag, like if the rest was hidden in the main tag.
val urlString = URL_TO_FETCH_IN_HTTPS (String)
val client = OkHttpClient()
val request = Request.Builder().url(urlString).build()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val body = response.body?.string()
println("RESPONSE " + body)
}
}
override fun onFailure(call: Call, e: IOException) {
println("Failure")
}
})
The response of this call is just
RESPONSE < ?xml version="1.0" encoding="UTF-8"?>< rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
Although the url is good, and the returned XML in a browser is also good.
So what do I do wrong in the code? How can I fetch the whole XML at this url?
I used the library OkHttp for fetching data from a URL
First of all to debug Okhttp i would suggest to add an interceptor :
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.HEADERS
logging.level = HttpLoggingInterceptor.Level.BODY
Then build you client like this to first add interceptor and have handle timeout :
client = OkHttpClient.Builder()
.addInterceptor(logging)
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
Then you can : client.newCall(request).enqueue(object : Callback { ...
By doing so you can easily debug Okhttp you will have on the log the request that you made with the parameter send + then response of the server with the code and the body. All call are writen in blue for better visibilitie.
if you still got probleme don't hesitate to ask.
More over you should look at retrofit2 it's an easy lib to handle all apicall.
I have an API which returns 200, 202, 4xx based on different scenarios. When I get a 202, I am supposed to make the same API until I get a 200 or 4xx. I tried using doOnErrorNext, onError, onNext. I was not able to crack the problem
Observable<MyPOJO> makeAPI();
Observable<MyPOJO> makeAPIImpl(){
makeAPI().doOnErrorNext(/*how to I access the error code here*/);
makeAPI().doOnNext(/*given that 202 is a success code, I expected it to come here, but it goes to Error because of JSON Mapping*/);
}
doOnErrorNext -> I was able to make the API call again but then it would happen for all the error scenarios which I dont want
I have checked multiple answers regarding this, but nobody has solved this combination specifically and am unable to incorporate other answers to my use case.
I would suggest you use OkHttp and use an interceptor to retry your request, something along these lines (this is from one of my apps in Kotlin, but it should give you the idea):
inner class ExpiredSessionInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if (response.code() == 202) {
val newRequest = request.newBuilder().build()
return chain.proceed(newRequest)
} else {
return response;
}
}
}
then
val httpClientBuilder = OkHttpClient.Builder()
httpClientBuilder.addInterceptor(ExpiredSessionInterceptor())
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(SERVER_ENDPOINT_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.client(httpClientBuilder.build())
.build()