3

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) 
Leonardo Sibela
  • 1,613
  • 1
  • 18
  • 39
j2emanue
  • 60,549
  • 65
  • 286
  • 456
  • Well, I guess you should firstly check https://stackoverflow.com/a/23503804/3225458 and implement interceptor as there mentioned. – romtsn Jun 18 '17 at 11:47

3 Answers3

5

Use a custom annotation

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Cacheable

class MainInterceptor : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val builder = request.newBuilder()
        request.tag(Invocation::class.java)?.let {
            if (!it.method().isAnnotationPresent(Cacheable::class.java)) {
                builder.cacheControl(CacheControl.Builder()
                    .noStore()
                    .build())
                return chain.proceed(builder.build())
            }
            try {
                builder.cacheControl(CacheControl.FORCE_NETWORK)
                return chain.proceed(builder.build())
            } catch (e: Throwable) {
                e.printStackTrace()
            }
            builder.cacheControl(CacheControl.Builder()
                .maxStale(Int.MAX_VALUE, TimeUnit.DAYS)
                .build())
        }
        return chain.proceed(builder.build())
    }
}


interface ServerApi {

    @Cacheable
    @GET("some_path")
    fun getSmth(): Call<ResponseBody>
}

Do not lost to add a cache to okhttp instance

OkHttpClient.Builder()
    .cache(Cache(context.cacheDir, 10 * 1024 * 1024L))
    .addInterceptor(MainInterceptor())
//...
Vlad
  • 7,997
  • 3
  • 56
  • 43
4

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

Josef Adamcik
  • 5,620
  • 3
  • 36
  • 42
  • how can i tell from the logs if its a cache copy ?i thought if i cut the internet i could tell easily but retrofit throws an exception that there is no network connection and i dont see or get anything api response. i also noticed my max-age was 0, is that important. i was letting okhttpclient configure the cache using the cache object it takes in the builder as per my post. – j2emanue Jun 18 '17 at 11:15
  • You can add logging interceptor as network interceptor (addNetworkInterceptor on Builder object of Ob). Network interceptor is called only when accessing network, so cached responses will not be displayed in log. I will expand the response to include this. – Josef Adamcik Jun 18 '17 at 12:25
  • And yes, Cache-Control: max-age=0 header sent by API is important and will force OkHttp to do the network request every time. – Josef Adamcik Jun 18 '17 at 12:35
  • thanks for the comment about network interceptor. i had to make two of them. one applicationinterceptor and one networkinterceptor and add them separately depending on what i need done on lifetime of request. thanks for that tip that logging does not show on cache response. i ended up adding a retrofit dynamic header so i can control the caching whenever i want. like this article explained https://krtkush.github.io/2016/06/02/caching-using-okhttp-part-2.html – j2emanue Jun 18 '17 at 14:13
1

I was also looking for same solution, them came up with this solution:

First create a Singleton Retrofit Class:

public class RetrofitClient {

private static final String BASE_URL = "BASE URL HERE...";

private static RetrofitClient mInstance;

private final Retrofit retrofit;
private final Retrofit retrofitCache;

private static final String HEADER_CACHE_CONTROL = "Cache-Control";
private static final String HEADER_PRAGMA = "Pragma";
int cacheSize = 10 * 1024 * 1024; // 10 MB

public static synchronized RetrofitClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RetrofitClient(context);
    }
    return mInstance;
}

private RetrofitClient(Context context) {
    retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    retrofitCache = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient(context))
            .addConverterFactory(GsonConverterFactory.create())
            .build();

}

public ApiInterface getApi() {
    return retrofit.create(ApiInterface.class);
}

public ApiInterface getCachedApi() {
    return retrofitCache.create(ApiInterface.class);
}

Interceptor networkInterceptor() {
    return chain -> {
        CacheControl cacheControl = new CacheControl.Builder()
                .maxAge(5, TimeUnit.MINUTES) //cache validity time
                .build();

        Response response = chain.proceed(chain.request());

        return response.newBuilder()
                .removeHeader(HEADER_PRAGMA)
                .removeHeader(HEADER_CACHE_CONTROL)
                .header(HEADER_CACHE_CONTROL, cacheControl.toString())
                .build();
    };
}

OkHttpClient okHttpClient(Context context) {
    Cache cache = new Cache(context.getCacheDir(), cacheSize);

    return new OkHttpClient.Builder()
            .cache(cache)
            .addNetworkInterceptor(networkInterceptor())
            .build();
}

}

Then create API interface like this:

public interface ApiInterface {

    @GET("apiEndPoint")
    Call<ResponseBody> getData();
}

Then use the RetrofitClient in Activity like this:

//Without Cache
Call<AboutResponse> call =
            RetrofitClient.getInstance(this)
                    .getApi()
                    .getAbout();

/With Cache
Call<AboutResponse> call =
            RetrofitClient.getInstance(this)
                    .getCachedApi()
                    .getAbout();
Kiran Banmala
  • 41
  • 1
  • 5