Android - Caching logic in Retrofit & OkHttp - android

This is what I am trying to implement:
If there is connection:
Data was updated more than 1 hour ago: update data
Data was updated less than 1 hour ago: get data from cache
If there is no connection:
Data was updated more than 48 hours ago: show message "No internet
connection"
Data was updated less than 48 hours ago: get data from cache
This is my REST client module where I tried to implement before stated logic.
#Module
public class DarkSkyApiModule {
public final String BASE_URL = "https://api.darksky.net";
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
if (NetworkUtil.isNetworkAvailable()) {
int maxAge = 60 * 60; // read from cache for 1 hour
return originalResponse.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 48; // tolerate 48-hours stale
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
}
};
#Provides
public OkHttpClient provideClient() {
//setup cache
File httpCacheDirectory = new File(FileManager.getCacheDir(), "responses");
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder()
.addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
.addInterceptor(interceptor)
.cache(cache)
.build();
}
#Provides
public Retrofit provideRetrofit(String baseURL, OkHttpClient client) {
return new Retrofit.Builder()
.baseUrl(baseURL)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
#Provides
public DarkSkyApiService provideApiService() {
return provideRetrofit(BASE_URL, provideClient()).create(DarkSkyApiService.class);
}
}
But result is not as expected:
If there is connection:
Data was updated more than 1 hour ago:
D/OkHttp: Cache-Control: public, max-age=3600
W/System.err: remove failed: ENOENT (No such file or directory) :
/data/user/0/com.android.example/cache/responses/2fb62bf59f85c3371d74d511a3bbeac2.0
W/System.err: remove failed: ENOENT (No such file or directory) : /data/user/0/com.android.example/cache/responses/2fb62bf59f85c3371d74d511a3bbeac2.1
D/OkHttp: [json_data]
Data was updated less than 1 hour ago:
D/OkHttp: Cache-Control: public, max-age=3600
W/System.err: remove failed: ENOENT (No such file or directory) :
/data/user/0/com.android.example/cache/responses/2fb62bf59f85c3371d74d511a3bbeac2.0
W/System.err: remove failed: ENOENT (No such file or directory) : /data/user/0/com.android.example/cache/responses/2fb62bf59f85c3371d74d511a3bbeac2.1
D/OkHttp: [json_data]
If there is no connection:
Data was updated more than 1 hour ago:
Unable to resolve host "api.darksky.net": No address associated with
hostname
Data was updated less than 48 hours ago:
W/System.err: remove failed: ENOENT (No such file or directory) : /data/user/0/com.android.example/cache/responses/journal.tmp
D/OkHttp: Cache-Control: public, max-age=3600
D/OkHttp: [json_data]
FileManager.java
public class FileManager {
static Context context;
public static void init(Context c) {
context = c;
}
public static File getCacheDir(){
return context.getCacheDir();
}
}
How to solve this problem? What I should set as max-age and max-stale?

Related

retrofit2.HttpException: HTTP 504 Unsatisfiable Request (only-if-cached) kotlin android

I want to cache the response for 2 hours and hence, tried adding cache to my okhttp client, every time hitting API with the network it works fine but next time, enabling airplane mode, we try fetching data in Offline mode, it gives retrofit2.HttpException: HTTP 504 Unsatisfiable Request (only-if-cached)
I have tried every solution related to this exception posted here on SO. Please help me find out what am I doing wrong...been stuck at this for really long.
interface TrendingRepoService {
#GET("/repositories?language=&since=daily")
suspend fun getTrendingRepos() : List<Repo>
}
object RetrofitClient {
private fun getClient(context: Context): Retrofit {
val cacheSize = (5 * 1024 * 1024).toLong() //cache size is 5mb
val myCache = Cache(File(context.cacheDir, "responses"), cacheSize)
val okHttpClient = OkHttpClient.Builder()
.cache(myCache)
.addInterceptor { chain ->
var request = chain.request()
if (hasNetwork(context)!!) {
request.newBuilder().removeHeader("Pragma")
.addHeader("Cache-Control", "public, max-age=" + 5)
} else {
request = request.newBuilder()
.removeHeader("Pragma")
.addHeader("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 2) //cache should expire after 2 hours
.build()
}
println("network: " + chain.proceed(request).networkResponse())
println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + myCache.directory().lastModified())
println("cache: " + chain.proceed(request).cacheResponse())
chain.proceed(request)
}
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://github-trending-api.now.sh")
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.build()
}
fun getTrendingService(context: Context): TrendingRepoService {
return getClient(context).create(TrendingRepoService::class.java)
}
}
I can see the following files in my app cache folder
The contents of file stored .0.tmp ending file in cache
https://github-trending-api.now.sh/repositories?language=&since=daily
GET
0
HTTP/1.1 200
14
date: Mon, 04 Nov 2019 19:27:43 GMT
content-type: application/json; charset=utf-8
x-powered-by: Express
access-control-allow-origin: *
etag: W/"4fcb-tcvKxcVttStpWpvGjIHTyk/TMbU"
cache-control: max-age=120
x-now-trace: hel1,bru1,sfo1
x-now-id: hel1:rv96m-1572895662521-476640337170
strict-transport-security: max-age=63072000
x-now-instance: 4008292682
content-encoding: gzip
server: now
OkHttp-Sent-Millis: 1572895661313
OkHttp-Received-Millis: 1572895662894
TLS_AES_256_GCM_SHA384
2
MIIFTzCCBDegAwIBAgISA4Bu+MYBP......
0
TLSv1.3
How max-age is 120?? Every time I am getting 504 error when I want to fetch data with cache.
When I hit API with no network only journal file exists in my cache folder the other two files are not there the next time.

Handle Multiple Requests in Retrofit?

I'm working on a App which needs to request 10 API Calls & return the response in an ArrayList.
I referred some question regarding Multiple Requests. Then i used Retrofit & Rx.
But It crashes saying HTTP FAILED: java.net.SocketTimeoutException: timeout
So Please Guide me...
LOGCAT
9657-9677 D/OkHttp: <-- HTTP FAILED: java.net.SocketTimeoutException: timeout
9657-9677 D/OkHttp: --> POST http://app.pay.com/paiay_features/recharge_bill/requestdetails.php http/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 42
9657-9657 W/System.err: io.reactivex.exceptions.OnErrorNotImplementedException: timeout
9657-9677 D/OkHttp: operaion=CheckCustomerDetails&userid=58155
--> END POST (42-byte body)
9657-9657 W/System.err: at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
9657-9677 D/OkHttp: <-- HTTP FAILED: java.io.IOException: Canceled
9657-9657 W/System.err: at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
9657-9657 W/System.err: at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
9657-9677 D/OkHttp: --> POST http://app.pay_features/recharge_bill/requestdetails.php http/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 42
9657-9657 W/System.err: at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
9657-9677 D/OkHttp: operaion=CheckCustomerDetails&userid=61885
9657-9677 D/OkHttp: --> END POST (42-byte body)
9657-9657 W/System.err: at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
9657-9677 D/OkHttp: <-- HTTP FAILED: java.io.IOException: Canceled
CODE
private RecyclerView recyclerView;
private PersonsAdapter adapter;
private List<Person> pList;
private ArrayList<String> balanceList = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
pList = new ArrayList<>();
adapter = new PersonsAdapter(this, pList);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
APIInterface apiInterface = APIClient.getClient().create(APIInterface.class);
Observable.zip(apiInterface.getDetails("CheckCustomerDetails", "61885"),
apiInterface.getDetails("CheckCustoDetails", "5815"),
apiInterface.getDetails("CheckCustoDetails", "6185"),
(u1, u2, u3) -> {
// prepare your returned users in a way suitable for further consumption
// in this case I put them in a list
return Arrays.asList(u1, u2, u3);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listOfUsers -> {
for (int i = 0; i < listOfUsers.size(); i++) {
String bal = listOfUsers.get(i).getUserAvailblnce().toString();
balanceList.add(bal);
}
preparePersons(balanceList);
});
}
private void preparePersons(List<String> balanceList) {
Log.e("preparePersons", "> >" + balanceList.size());
Person a = new Person("Arhan", "96776", balanceList.get(0));
pList.add(a);
a = new Person("Raja", "93843", balanceList.get(1));
pList.add(a);
adapter.notifyDataSetChanged();
}
MODEL
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("http://app.paiy.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(client)
.build();
return retrofit;
}
}
INTERFACE
interface APIInterface {
#FormUrlEncoded
#POST("bill/requestdetails.php")
Observable<User> getDetails(#Field("operaion") String value, #Field("userid") String id);
}
You probably haven't added the right call adapter
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
add the adapter to you retrofit object
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

Retrofit response : Cache Control. What does it mean?

I use retrofit and picasso library. Picasso manages caching itself. But when I view logcat, I see log below. What does it mean? Doesn't the webservice and backend send cache info correctly? How can I solve this to make Retrofit cache correctly. So, Does this data make sense?
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Type: application/json
Date: Wed, 14 Dec 2016 07:15:48 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
OkHttp-Received-Millis: 1481597410805
OkHttp-Response-Source: NETWORK 200
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1481597409021
Pragma: no-cache
Server: Apache
Transfer-Encoding: chunked
You can set cache option to OkHttp with using Interceptor.
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new CachingControlInterceptor());
Retrofit restAdapter = new Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
and CachingControlInterceptor is:
public class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (ConnectivityUtil.checkConnectivity(YaootaApplication.getContext())) {
// 1 day
request = request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
} else {
// 4 weeks stale
request = request.newBuilder()
.header("Cache-Control", "public, max-stale=2419200")
.build();
}
}
Response originalResponse = chain.proceed(request);
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=600")
.build();
}
}
See this:
https://stackoverflow.com/a/34401686/850347

HTTP Caching with Retrofit 2.0.x

I'm trying to cache some responses in my app using Retrofit 2.0, but I'm missing something.
I installed a caching file as follows:
private static File httpCacheDir;
private static Cache cache;
try {
httpCacheDir = new File(getApplicationContext().getCacheDir(), "http");
httpCacheDir.setReadable(true);
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
HttpResponseCache.install(httpCacheDir, httpCacheSize);
cache = new Cache(httpCacheDir, httpCacheSize);
Log.i("HTTP Caching", "HTTP response cache installation success");
} catch (IOException e) {
Log.i("HTTP Caching", "HTTP response cache installation failed:" + e);
}
public static Cache getCache() {
return cache;
}
which creates a file in /data/user/0/<PackageNmae>/cache/http
, then prepared a network interceptor as follows:
public class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (ConnectivityUtil.checkConnectivity(getContext())) {
// 1 day
request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
} else {
// 4 weeks stale
request.newBuilder()
.header("Cache-Control", "public, max-stale=2419200")
.build();
}
}
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=86400")
.build();
}
}
my Retrofit and OkHttpClient instance:
OkHttpClient client = new OkHttpClient();
client.setCache(getCache());
client.interceptors().add(new MainInterceptor());
client.interceptors().add(new LoggingInceptor());
client.networkInterceptors().add(new CachingControlInterceptor());
Retrofit restAdapter = new Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
productsService = restAdapter.create(ProductsService.class);
where ProductsService.class contains:
#Headers("Cache-Control: max-age=86400")
#GET("categories/")
Call<PagedResponse<Category>> listCategories();
and
Call<PagedResponse<Category>> call = getRestClient().getProductsService().listCategories();
call.enqueue(new GenericCallback<PagedResponse<Category>>() {
// whatever
// GenericCallback<T> implements Callback<T>
}
});
The question here is: How to make it access cached responses when device being offline?
Header of backend response are:
Allow → GET, HEAD, OPTIONS
Cache-Control → max-age=86400, must-revalidate
Connection → keep-alive
Content-Encoding → gzip
Content-Language → en
Content-Type → application/json; charset=utf-8
Date → Thu, 17 Dec 2015 09:42:49 GMT
Server → nginx
Transfer-Encoding → chunked
Vary → Accept-Encoding, Cookie, Accept-Language
X-Frame-Options → SAMEORIGIN
x-content-type-options → nosniff
x-xss-protection → 1; mode=block
Finally I get the answer.
Network Interceptor should be as follow:
public class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (ConnectivityUtil.checkConnectivity(YaootaApplication.getContext())) {
// 1 day
request = request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
} else {
// 4 weeks stale
request = request.newBuilder()
.header("Cache-Control", "public, max-stale=2419200")
.build();
}
}
Response originalResponse = chain.proceed(request);
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=600")
.build();
}
}
then installing cache file is that simple
long SIZE_OF_CACHE = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(new File(context.getCacheDir(), "http"), SIZE_OF_CACHE);
OkHttpClient client = new OkHttpClient();
client.cache(cache);
client.networkInterceptors().add(new CachingControlInterceptor());
In your CachingControlInterceptor, you create new requests, but never actually use them. You call newBuilder and ignore the result, so the header modification is never actually sent any where. Try assigning those values to request and then instead of calling proceed on chain.request() call it on request.
public class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (ConnectivityUtil.checkConnectivity(getContext())) {
// 1 day
request = request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
} else {
// 4 weeks stale
request = request.newBuilder()
.header("Cache-Control", "public, max-stale=2419200")
.build();
}
}
Response originalResponse = chain.proceed(request);
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=600")
.build();
}
}
you can also try:
public class CachingInterceptor implements Interceptor {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxAge(1, TimeUnit.DAYS)
.minFresh(4, TimeUnit.HOURS)
.maxStale(8, TimeUnit.HOURS)
.build())
.url(request.url())
.build();
return chain.proceed(request);
}
}
I finally discovered the solution that worked for me in Retrofit 2.x and OkHttp 3.x
I had to implement two Interceptors, one of them is responsible to rewrite the Request headers and the other to rewrite the Response headers.
First, make sure you delete any old cache. (root explorer /data/data/com.yourapp/cache
Instantiate the client builder:
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new RewriteRequestInterceptor())
.addNetworkInterceptor(new RewriteResponseCacheControlInterceptor())
Create the RewriteRequestInterceptor
public class RewriteRequestInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
int maxStale = 60 * 60 * 24 * 5;
Request request;
if (NetworkUtils.isNetworkAvailable()) {
request = chain.request();
} else {
request = chain.request().newBuilder().header("Cache-Control", "max-stale=" + maxStale).build();
}
return chain.proceed(request);
}
}
Create the RewriteResponseCacheControlInterceptor
public class RewriteResponseCacheControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
int maxStale = 60 * 60 * 24 * 5;
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder().header("Cache-Control", "public, max-age=120, max-stale=" + maxStale).build();
}
}
It's important to make sure you add the ResponseCacheControlInterceptor as a Network Interceptor, and the RewriteRequestInterceptor as a Interceptor (as I did in the 2nd step).
OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();

Retrofit + OkHTTP - response cache not working

I know there has been a lot of similar questions, but I have read them all and none of them really helped.
So, here is my problem:
I am using retrofit + okhttp to fetch some data from API and I'd like to cache them. Unfortunately, I don't have admin access to the API server so I can't modify headers returned by the server. (currently, server returns Cache-control: private)
So I decided to use okhttp header spoofing to insert appropriate cache headers. Sadly, no matter what I do, caching doesn't seem to work.
I initialise the api service like this:
int cacheSize = 10 * 1024 * 1024; // 10 MiB
File cacheFile = new File(context.getCacheDir(), "thumbs");
final Cache cache = new Cache(cacheFile, cacheSize);
OkHttpClient client = new OkHttpClient();
client.setCache(cache);
client.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.removeHeader("Access-Control-Allow-Origin")
.removeHeader("Vary")
.removeHeader("Age")
.removeHeader("Via")
.removeHeader("C3-Request")
.removeHeader("C3-Domain")
.removeHeader("C3-Date")
.removeHeader("C3-Hostname")
.removeHeader("C3-Cache-Control")
.removeHeader("X-Varnish-back")
.removeHeader("X-Varnish")
.removeHeader("X-Cache")
.removeHeader("X-Cache-Hit")
.removeHeader("X-Varnish-front")
.removeHeader("Connection")
.removeHeader("Accept-Ranges")
.removeHeader("Transfer-Encoding")
.header("Cache-Control", "public, max-age=60")
//.header("Expires", "Mon, 27 Apr 2015 08:15:14 GMT")
.build();
}
});
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_ROOT)
.setLogLevel(RestAdapter.LogLevel.HEADERS_AND_ARGS)
.setClient(new OkClient(client))
.setConverter(new SimpleXMLConverter(false))
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
if (Network.isConnected(context)) {
int maxAge = 60; // read from cache for 2 minutes
request.addHeader("Cache-Control", "public, max-age=" + maxAge);
} else {
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
request.addHeader("Cache-Control",
"public, only-if-cached, max-stale=" + maxStale);
}
}
})
.build();
api = restAdapter.create(ApiService.class);
Of course, it's not necessary to remove all these headers, but I wanted to make the response as clean as possible to rule out some interference from these extra headers.
As you can see, I tried to also spoof Expires and Date header (I tried removing them, setting them so that there is exactly max-age differnece between them and also setting Expires far into future). I also experimented with various Cache-control values, but no luck.
I made sure the cacheFile exists, isDirectory and is writeable by the application.
These are the request and response headers as logged directly by retrofit:
Request:
Cache-Control: public, max-age=60
---> END HTTP (no body)
Response:
Date: Mon, 27 Apr 2015 08:41:10 GMT
Server: Apache/2.2.22 (Ubuntu)
Expires: Mon, 27 Apr 2015 08:46:10 GMT
Content-Type: text/xml; charset=UTF-8
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1430124070000
OkHttp-Received-Millis: 1430124070040
Cache-Control: public, max-age=60
<--- END HTTP (-1-byte body)
<--- BODY: ...
And, finally one strange incident: At some point, the cache worked for a few minutes. I was getting reasonable hit counts, even offline requests returned cached values. (It happened while using the exact setting posted here) But when I restarted the app, everything was back to "normal" (constant hit count 0).
Co if anyone has any idea what could be the problem here, I'd be really glad for any help :)
Use networkInterceptors() instead of interceptors(). That in combination with your strategy of removing any headers that are somewhat related to caching will work. That's the short answer.
When you use interceptors to change headers it does not make any adjustments before CacheStrategy.isCacheable() is called. It's worthwhile to look at the CacheStrategy and CacheControl classes to see how OKHttp handles cache-related headers. It's also worthwhile to do ctrl+f "cache" on http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
I am not sure if the networkInterceptors() and interceptors() documentation is just unclear or if there is a bug. Once I look into that more, I will update this answer.
One more thing to add here, Apart from Brendan Weinstein's answer just to confirm OkHttp3 cache will not work with post requests.
After a full day, I found that my offline caching was not working just because I was using POST in the API type. The moment I changed it to GET, it worked!
#GET("/ws/audioInactive.php")
Call<List<GetAudioEntity>> getAudios();
My entire Retrofit class.
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.limnet.iatia.App;
import com.limnet.iatia.netio.entity.registration.APIInterfaceProviderIMPL;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RHTRetroClient {
public static final String BASE_URL = "https://abc.pro";
private static Retrofit retrofit = null;
private static RHTRetroClient mInstance;
private static final long cacheSize = 10 * 1024 * 1024; // 10 MB
public static final String HEADER_CACHE_CONTROL = "Cache-Control";
public static final String HEADER_PRAGMA = "Pragma";
private RHTRetroClient() {
Gson gson = new GsonBuilder()
.setLenient()
.create();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
Cache cache = new Cache(new File(App.getAppContext().getCacheDir(), "soundbites"),cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(httpLoggingInterceptor()) // used if network off OR on
.addNetworkInterceptor(networkInterceptor()) // only used when network is on
.addInterceptor(offlineInterceptor())
.build();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
/**
* This interceptor will be called both if the network is available and if the network is not available
*
* #return
*/
private static Interceptor offlineInterceptor() {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Log.d("rht", "offline interceptor: called.");
Request request = chain.request();
// prevent caching when network is on. For that we use the "networkInterceptor"
if (!App.hasNetwork()) {
CacheControl cacheControl = new CacheControl.Builder()
.maxStale(7, TimeUnit.DAYS)
.build();
request = request.newBuilder()
.removeHeader(HEADER_PRAGMA)
.removeHeader(HEADER_CACHE_CONTROL)
.cacheControl(cacheControl)
.build();
}
return chain.proceed(request);
}
};
}
/**
* This interceptor will be called ONLY if the network is available
*
* #return
*/
private static Interceptor networkInterceptor() {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Log.d("rht", "network interceptor: called.");
Response response = chain.proceed(chain.request());
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(5, TimeUnit.SECONDS)
.build();
return response.newBuilder()
.removeHeader(HEADER_PRAGMA)
.removeHeader(HEADER_CACHE_CONTROL)
.header(HEADER_CACHE_CONTROL, cacheControl.toString())
.build();
}
};
}
private static HttpLoggingInterceptor httpLoggingInterceptor() {
HttpLoggingInterceptor httpLoggingInterceptor =
new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
#Override
public void log(String message) {
Log.d("rht", "log: http log: " + message);
}
});
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return httpLoggingInterceptor;
}
public static synchronized RHTRetroClient getInstance() {
if (mInstance == null) {
mInstance = new RHTRetroClient();
}
return mInstance;
}
public APIInterfaceProviderIMPL getAPIInterfaceProvider() {
return retrofit.create(APIInterfaceProviderIMPL.class);
}
}
Check if there is a Pragma header in your response. Caching with max-age will not work if Pragma: no-cache header is present.
If it does have Pragma header, remove it by doing the following in your Interceptor:
override fun intercept(chain: Interceptor.Chain): Response {
val cacheControl = CacheControl.Builder()
.maxAge(1, TimeUnit.MINUTES)
.build()
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl.toString())
.removeHeader("Pragma") // Caching doesnt work if this header is not removed
.build()
}

Categories

Resources