Search code examples
androidretrofit2okhttpcache-control

Offline cache okhttp + retrofit not working


I am trying to activate a cache for offline browsing when no network is available. Unfortunatly it is somehow not working, when I debug the app, the interceptor nor cache is being accessed.

So I guess something is wrong with below code.

public class ApiHelper {

    Context context;
    private static final int TIMEOUT = 30;
    private static final int WRITE_TIMEOUT = 30;
    private static final int CONNECT_TIMEOUT = 10;
    //private static final int CACHE_SIZE = 500 * 1024 * 1024;
    private static final int CACHE_SIZE = 10 * 1024 * 1024;

    public static final String baseUrl = "http://test.com/api-new/";

    public ApiHelper(Context context) {
        this.context = context;
    }

    private boolean isNetworkAvailable(Context context) {
        ConnectivityManager connectivityManager
                = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }


    // Configure access cache when offline
    private final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Response originalResponse = chain.proceed(chain.request());
            if (isNetworkAvailable(context)) {
                int maxAge = 60; // read from cache for 1 minute
                return originalResponse.newBuilder()
                        .removeHeader("Pragma")
                        .header("Cache-Control", "public, max-age=" + maxAge)
                        .build();
            } else {
                int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
                return originalResponse.newBuilder()
                        .removeHeader("Pragma")
                        .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                        .build();
            }
        }
    };


    private static OkHttpClient CLIENT = new OkHttpClient();

    File httpCacheDirectory = new File(context.getCacheDir(), "HttpCache");
    Cache cache = new Cache(httpCacheDirectory, CACHE_SIZE);

     {
        CLIENT = new OkHttpClient.Builder().
                connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS).
                writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS).
                readTimeout(TIMEOUT, TimeUnit.SECONDS).
                authenticator(new Authenticator()).
                addInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR).
                cache(cache).
                addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request request;

                        UserModel user = HelperFactory.getDatabaseHelper().getUserDao().getCurrentUser();
                        if (user != null && user.getAccess_token() != null)
                            request = chain.request().newBuilder()
                                    .addHeader("X-Auth-Token", user.getAccess_token()).build();
                        else
                            request = chain.request().newBuilder().build();

                        long t1 = System.nanoTime();
                        Logger.debug(ApiHelper.class.getName(), String.format("Sending request %s on %s%n",
                                request.url(), chain.connection()));

                        UserModel currentUser = HelperFactory.getDatabaseHelper().getUserDao().getCurrentUser();
                        if (currentUser != null)
                            Logger.debug(ApiHelper.class.getName(), "Sending request header " + request.headers().toString());

                        if (request != null && request.body() != null && request.body().contentLength() > 0) {
                            Buffer buffer = new Buffer();
                            request.body().writeTo(buffer);
                            String body = buffer.readUtf8();
                            Logger.debug(ApiHelper.class.getName(), "Sending request body " + body);
                        }

                        Response response = chain.proceed(request);
                        String msg = response.body().string();

                        long t2 = System.nanoTime();

                        Logger.debug(ApiHelper.class.getName(), String.format("Received response %s in %.1fms%nResponse code:%s%nReceived data: %s",
                                response.request().url(), (t2 - t1) / 1e6d, response.code(), msg));

                        return response.newBuilder()
                                .body(ResponseBody.create(response.body().contentType(), msg))
                                .headers(response.headers())
                                .build();
                    }
                }).build();
    }

    @NonNull
    public static MyApplication getInstance() {
        Retrofit rf = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create(new GsonBuilder()
                        .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC)
                        .create()))
                .client(CLIENT)
                .build();

        return rf.create(MyApplication.class);
    }


}

Solution

  • You initialization block is non-static, but you never allocate an instance of this object, so it never gets run. It looks like you intended that to initialize the static member CLIENT. Because this initialization ultimately depends on a Context you can't convert that to a static initialization block. Convert it to an initialization method that takes a Context parameter. Note: I also moved cache and httpCacheDir into this method as local variables.

    private static boolean isNetworkAvailable(Context context) {
            ConnectivityManager connectivityManager
                    = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
            return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }
    
    // Configure access cache when offline
    private static Interceptor getInterceptor(Context context) {
        return new Interceptor() {
        @Override
        //....
        }
    }
    
    private static void initClient(Context context) {
        httpCacheDirectory = File(context.getCacheDir(), "HttpCache");
        Cache cache = new Cache(httpCacheDirectory, CACHE_SIZE);
        CLIENT = new OkHttpClient.Builder().
                connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS).
                writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS).
                readTimeout(TIMEOUT, TimeUnit.SECONDS).
                authenticator(new Authenticator()).
                addInterceptor(getInterceptor((context)).
                cache(cache).
                addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request request;
    
                        UserModel user = HelperFactory.getDatabaseHelper().getUserDao().getCurrentUser();
                        if (user != null && user.getAccess_token() != null)
                            request = chain.request().newBuilder()
                                    .addHeader("X-Auth-Token", user.getAccess_token()).build();
                        else
                            request = chain.request().newBuilder().build();
    
                        long t1 = System.nanoTime();
                        Logger.debug(ApiHelper.class.getName(), String.format("Sending request %s on %s%n",
                                request.url(), chain.connection()));
    
                        UserModel currentUser = HelperFactory.getDatabaseHelper().getUserDao().getCurrentUser();
                        if (currentUser != null)
                            Logger.debug(ApiHelper.class.getName(), "Sending request header " + request.headers().toString());
    
                        if (request != null && request.body() != null && request.body().contentLength() > 0) {
                            Buffer buffer = new Buffer();
                            request.body().writeTo(buffer);
                            String body = buffer.readUtf8();
                            Logger.debug(ApiHelper.class.getName(), "Sending request body " + body);
                        }
    
                        Response response = chain.proceed(request);
                        String msg = response.body().string();
    
                        long t2 = System.nanoTime();
    
                        Logger.debug(ApiHelper.class.getName(), String.format("Received response %s in %.1fms%nResponse code:%s%nReceived data: %s",
                                response.request().url(), (t2 - t1) / 1e6d, response.code(), msg));
    
                        return response.newBuilder()
                                .body(ResponseBody.create(response.body().contentType(), msg))
                                .headers(response.headers())
                                .build();
                    }
                }).build();
    }
    

    Then you will need to also take a Context to you getInstance method and then call initClient --

    @NonNull
    public static MyApplication getInstance(Context context) {
        initClient(context);
        Retrofit rf = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create(new GsonBuilder()
                        .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC)
                        .create()))
                .client(CLIENT)
                .build();
    
        return rf.create(MyApplication.class);
    }
    

    You can also delete your constructor and the context data member as they are not used.