Search code examples
androidcachinghttp-headersretrofit2

Retrofit2 - how to cache only certain API calls


I'd like to just cache certain calls not all. This is the code i have now but it will affect all retrofit calls:

int cacheSize = 10 * 1024 * 1024; // 10 MB  
Cache cache = new Cache(getCacheDir(), cacheSize);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cache(cache)
    .build();

Retrofit.Builder builder = new Retrofit.Builder()  
    .baseUrl("http://10.0.2.2:3000/")
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create());

Retrofit retrofit = builder.build(); 

Is there something i can put in the header maybe when i want to just do caching for that call ?

For example:

// i was thinking i would be enough to put something in the header. I only want to cache the calls on the client (android) end. so i was thinking retrofit could remember the response and cache it for the next call, but i dont want it for all my calls, just the ones i want, maybe 1 or 2. the rest can connect to server all the time. How is this acheived ?

@Headers("Cache-Control:????XXXXX) //is it possible this way ?, how ?
@GET("getBusiness.action")// Store information 
Call<RestaurantInfoModel> getRestaurantInfo(@Query("userId") String userId,@Query("businessId") String businessId);

UPDATE:

HERE IS WHAT I HAVE TRIED NOW:

Here is how i build out the okhttpclient:

final OkHttpClient.Builder builder = new OkHttpClient.Builder();
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(getCacheDir(), cacheSize);
if (BuildConfig.RETROFIT_LOG_ALL) {
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    builder.addInterceptor(logging);
}
builder.cache(cache);
return builder.build();

later on i add it to retrofit and it works but not with caching it seems. Lets see the header response without adding caching to okhttp:

Content-Type: application/json; charset=utf-8
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Connection: close
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Transfer-Encoding: chunked
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: X-Powered-By: Express
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Vary: Origin
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Access-Control-Allow-Credentials: true
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Cache-Control: public, max-age=0
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: ETag: W/"39ba-G9evSsiVDp9GAVGu1Mk4ag"
06-18 17:54:58.044 11233-11735/com.mobile.myapp.staging D/OkHttp: Date: Sun, 18 Jun 2017 10:54:58 GMT

So here is the test i did to see if caching is working. i made a request to the api using this:

public interface CountriesApi {
    @GET("countries")
    @Headers({"Content-Type:application/json"})
    Observable<List<CountryModel>> getCountries();
}

then afterwards i SHUTDOWN the internet on the android device and tried to do teh same call again while still in the app of course. But instead i got retrofit complaining that there is no network connection. instead it should have just gotten from the cache. any idea whats wrong ?

java.net.SocketException: Network is unreachable at
java.net.PlainSocketImpl.socketConnect(Native Method) at
java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:334) at
java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:196)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:178) at
java.net.SocksSocketImpl.connect(SocksSocketImpl.java:356) at
java.net.Socket.connect(Socket.java:586) at
okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.java:63)
at
okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:223)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:149)
at
okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:192)
at
okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)
at
okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)
at
okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at
okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:211)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at
com.mobile.retrofit.NetworkSessionManager.intercept(NetworkSessionManager.java:38)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185) at
okhttp3.RealCall.execute(RealCall.java:69) at
retrofit2.OkHttpCall.execute(OkHttpCall.java:180) at
retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:41)
at io.reactivex.Observable.subscribe(Observable.java:10842) at
retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:10842) at
io.reactivex.internal.operators.observable.ObservableFlatMap.subscribeActual(ObservableFlatMap.java:55)
at io.reactivex.Observable.subscribe(Observable.java:10842) at
io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452) at
io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
at
io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
at java.util.concurrent.FutureTask.run(FutureTask.java:237) at
java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761) 

Solution

  • It's the OkHttp library who does the caching (doc) and it's driven mainly by response headers. You should check them and verify if API responses are cached at all. OkHttp logging interceptor should help with debugging.

    To check if network request is happening use HttpLoggingInterceptor as network interceptor, not application interceptor (see https://github.com/square/okhttp/wiki/Interceptors):

    HttpLoggingInterceptor interceptor = HttpLoggingInterceptor();
    interceptor.setLevel(Level.BASIC);
    OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();
    

    Network interceptor will not be called when request is handled by cache.

    Next step will depend on the situation.

    1) All API requests are cached.

    Then you should be able to use annotation @Headers("Cache-Control: no-cache") for methods you don't want to cache.

    2) All API request are not cached.

    Then you can either change your API (adding proper cache headers) or you can implement network interceptor for OkHttp which will modify responses (headers). You can find some inspiration in this response: Can Retrofit with OKHttp use cache data when offline