Use cached response only. Without network call - android

There is an API that has calls limit, in this case I want to cache response and don't run network response if cache is still valid.
First of all I have cache interceptor
fun provideCacheInterceptor(): Interceptor = Interceptor { chain ->
val response = chain.proceed(chain.request())
val cacheControl = CacheControl.Builder()
.maxAge(6, TimeUnit.HOURS)
.maxStale(6, TimeUnit.HOURS)
.onlyIfCached()
.build()
response.newBuilder()
.header("CacheControl", cacheControl.toString())
.build()
}
I attach cache and interceptor to the client
client = OkHttpClient().newBuilder()
.cache(cache)
.addInterceptor(loggingInterceptor)
.addInterceptor(cacheInterceptor)
As I result when I'm trying to check if the reponse from cache and/or from network
Log.e("!##", "cached: ${it.raw().cacheResponse()?.toString()}")
Log.e("!##", "network: ${it.raw().networkResponse()?.toString()}")
I get
cached: Response{protocol=http/1.1, code=200, message=, url=https://API}
network: Response{protocol=h2, code=200, message=, url=https://API}
Is there any way not to call the network endpoint if cache is still valid?

Problem in my implementation that I proceed the original request and apply header to response (my bad), need to modify initial request and proceed modified request then.
Also seems like Cache-Control header typo.
The proper cache interceptor looks like
fun provideCacheInterceptor(networkManager: NetworkManager): Interceptor = Interceptor { chain ->
val request = chain.request()
val cacheControl = CacheControl.Builder()
.maxAge(6, TimeUnit.HOURS)
.maxStale(6, TimeUnit.HOURS)
.build()
chain.proceed(request.newBuilder()
.header("Cache-Control", cacheControl.toString())
.build())
}

Related

Android HTTP Headers with API

What are headers used for if I want to do a post request & response ?
How is it possible to "save" token there ?
I can't find any good explanation about it.
you can use Retrofit to call api and store token into shared preferences and add common headers to OkhttpClient
val prefs = Prefs.getInstance();
val httpClient = OkHttpClient.Builder()
httpClient.addInterceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.header("Authorization", prefs.token)
.header("Accept", "application/json")
.method(original.method, original.body)
.build()
chain.proceed(request)
}
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
httpClient.addInterceptor(interceptor)
val client = httpClient.build()
and make Retrofit object like this
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder().setPrettyPrinting().create()
)
)
.client(client).build()

Retrofit Android - add header to existing Retrofit object

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()

what is retrofit interceptor , can we have multiple?

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!

OkHttp add Basic Authentication Header

I want to add Basic Authentication header to my request done with OkHttp3.
Here is my code:
// Adding Authentication Header
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.authenticator(new Authenticator() {
#Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(username, password);
return response.request().newBuilder().header("Authorization", credential).build();
}
});
//
client.connectTimeout(10, TimeUnit.SECONDS);
client.writeTimeout(10, TimeUnit.SECONDS);
client.readTimeout(10, TimeUnit.MINUTES);
RequestBody body = RequestBody.create(null, new byte[]{});
if( json != null) {
body = RequestBody.create(MediaType.parse(
"application/json"),
json.toString()
);
}
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
client.build().newCall(request).enqueue(callback);
Unfortunately no Authentication header is added and I really can't find the mistake.
Authenticator is primarily intended for "reactive" authentication, i.e. it is not called automatically and every time. The main scenario for its automatic invocation is a 401 "Unauthorized" server response.
You should probably use a regular Interceptor instead for your case, just register it using your client builder like this:
client.addInterceptor(
object : Interceptor { chain ->
val request = chain.request().newBuilder()
.addHeader(...)
.build()
return chain.proceed(request)
}
)
In order to do that, you need to use Interceptors offered by OkHttp Builder. Interceptors, as the name suggests, can intercept and requests sent through the client or any response received by the client before it passes them to your application.
In your case, you need to add this custom interceptor by intercepting the request, and attaching a new header to it before it leaves your client.
Adding a custom header to every request
return OkHttpClient.Builder()
.addInterceptor { chain ->
var request = chain.request()
var url = request.url()
request = request.newBuilder()
.removeHeader("needAuthToken")
.addHeader("Content-Type", "application/json")
// Feel free to add any other headers
.url(url)
.build()
chain.proceed(request)
}
Feel free to wrap this in any control flow conditions in case attachment of these headers matter on a predicate.

Receiving incomplete response from retrofit

I am using retrofit version 2.6.1 for making http requests over the network. The JSON I am expecting is 42466 characters long. However, I am receiving only 4073 characters and API are working fine on web browser and postman.
So I added custom okhttp client and increase timeout but it does not help me.
private var okHttpClient: OkHttpClient = OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
Then I tried adding a logging interceptor and I found that okhttp is receiving the response what I wanted in interceptor logs but in chunks.
private val httpInterceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
private var okHttpClient: OkHttpClient = OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.addInterceptor(httpInterceptor)
.build()
At last, I assigned http client and interceptor to retrofit builder and this is how it's look
private val centralRetrofit = Retrofit.Builder().baseUrl("https://www.********.com/")
.addConverterFactory(ScalarsConverterFactory.create())
.client(okHttpClient)
.build()
.create(MusicAccess::class.java)
So, I think using post request will help me rather than get and tried taking all the response in string format to check the reponse
#Headers("Content-Type: text/html; charset=UTF-8")
#POST("**********")
fun getMusic(): Call<String>
But it also did not get to conclusion after that I thought http response will have size limit and used reader to access the json from url by following way.
val client = OkHttpClient()
val request = Request.Builder().url("********")
.addHeader("Content-Type", "application/json")
.build()
val response = client.newCall(request).execute()
val input = response.body()?.byteStream()
val reader = BufferedReader(InputStreamReader(input))
But as this https://stackoverflow.com/a/26023885/7639056 answer state that we cannot configure the OkHttpClient to read more than 2048 bytes from the buffer
So is there any way I can get all the data at once?
Eventually I figured out that why this is happening.
There were no problem with the HTTP request. But only a part of it was being printed in logcat due to it's limited buffer size.
There is a size limit of 1024 bytes for binary logs. The size limit for non-binary logs are as shown below.
#define LOGGER_ENTRY_MAX_LEN (4*1024)
#define LOGGER_ENTRY_MAX_PAYLOAD (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))
To set the limit to a larger number, Preferences/Settings -> Editor -> General -> Console, check the box next to Override console cycle buffer size, and enter the number.
I am feeling so embarrassed, but thank you for your support.

Categories

Resources