Caching with Retrofit (Make your app offline)

In caching, it will hit to server periodically means if same network resource again after having accessed it recently, your device won’t need to make a request to the server; it’ll get the cached response instead.

Caching with Retrofit (Make your app offline)

Caching with Retrofit

Please download project and sample here.  CachingWithRetrofit

Benefits of Caching :

1. Avoid server hitting and response.

2. Saves you time you’d spend waiting for the server to give you the network response.

3. Reduces device data consumption.

4. Application will work offline with full data.

 

Currently we are using retrofit like :

retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

 

Above retrofit using default OkHttpClient which is not using cache so we have to create  OkHttpClient class and enable cache and interceptor according to us.

OkHttpClient allow us to override cache.

A. First Create cache size and cache.

private static final int HTTP_CACHE_MAX_SIZE = 1 * 1024 * 1024;   // 1 MB

File cacheDir = new File(CachingWithRetrofitApp.getAppContext().getCacheDir(), HTTP_CACHE_DIR);
Cache myCache  = new Cache(cacheDir, HTTP_CACHE_MAX_SIZE)

 

B. Lets create interceptor :

// this for only response interceptor is invoked for online responses

private static final Interceptor REWRITE_RESPONSE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());
        int maxAge = originalResponse.cacheControl().maxAgeSeconds();
        if (maxAge <= 0) {
            return originalResponse.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Expires")
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", String.format(Locale.ENGLISH, "max-age=%d, only-if-cached, max-stale=%d", CACHE_DURATION_SEC, 0))
                    .build();
        } else {
            return originalResponse;
        }
    }
};

C. Define internet interceptor : 

private static final Interceptor rewriteRequestInterceptor = new Interceptor() {
    @Override
    public Response intercept(final Chain chain) throws IOException {
        Request request = chain.request();

        if (request.cacheControl().noCache()) {
            return chain.proceed(request);
        }

        if (Utils.isNetworkConnected(CachingWithRetrofitApp.getAppContext())) {
            // the data can be reused for CACHE_DURATION_MIN
            request = request.newBuilder()
                    .cacheControl(new CacheControl.Builder().maxStale(CACHE_DURATION_MIN, TimeUnit.MINUTES).build())
                    .build();
        }
        else {
            // for offline
            request = request.newBuilder()
                    .cacheControl(new CacheControl.Builder().onlyIfCached()
                            .maxStale(STALE_DURATION_DAYS,
                                    TimeUnit.DAYS)
                            .build())
                    .build();
        }

        return chain.proceed(request);
    }
};

 

D. Now create OkHttpClient and define cache and add interceptor.

OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder.connectTimeout(TIMEOUT, TimeUnit.SECONDS);
okHttpClientBuilder.cache(myCache);
okHttpClientBuilder.addInterceptor(rewriteRequestInterceptor);
okHttpClientBuilder.addNetworkInterceptor(REWRITE_RESPONSE_CACHE_CONTROL_INTERCEPTOR);

 

Now, Lets implement it step by step in sample application.

 

1. Implement retro fit dependency :

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "datanapps.caching.retrofit"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:design:28.0.0'

    // implement gson
    implementation 'com.google.code.gson:gson:2.8.5'

    // retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'

    // gson converter
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'

    // butter knife
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

    // Picasso for image loading
    implementation 'com.squareup.picasso:picasso:2.71828'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

 

2. Introduce internet in manifest file.

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="datanapps.caching.retrofit">


    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:name=".CachingWithRetrofitApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">


        <activity android:name=".ui.UserListActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

3. Create Utils class to check network connection :

package datanapps.caching.retrofit.utils;

        import android.content.Context;
        import android.net.ConnectivityManager;
        import android.net.NetworkInfo;

public class Utils {

    public static boolean isNetworkConnected( Context context) {
        ConnectivityManager cm =
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        return activeNetwork != null &&
                activeNetwork.isConnectedOrConnecting();
    }
}

4. Create Retrofit class :

public class RetrofitClient {

    private static final String BASE_URL = "https://datanapps.com";

    private static final int TIMEOUT = 10;

    /**
     * The overridden cache duration to keep data from GET requests.
     * Currently set to 10 minutes
     */
    private static final int CACHE_DURATION_MIN = 10;

    /**
     * The overridden cache duration to keep data from GET requests.
     * Currently set to 10 minutes
     */
    private static final long CACHE_DURATION_SEC = 600;

    /**
     * The overridden stale duration in seconds.
     * Currently set to 7 days
     */
    private static final int STALE_DURATION_DAYS = 7;

    /**
     * Max size of OkHTTP cache -- currently 50 MB
     */
    private static final int HTTP_CACHE_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * The directory name to place cache files from OkHttp. This directory will be placed in the
     * app's internal cache directory (or external if this is a debug build)
     */
    private static final String HTTP_CACHE_DIR = "cachingwithretrofit_cache";



    public static Retrofit retrofit;


    public static Retrofit getRetrofitClient() {
        if (retrofit == null) {
            Cache myCache = getHttpCache();
            OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
            okHttpClientBuilder.connectTimeout(TIMEOUT, TimeUnit.SECONDS);
            okHttpClientBuilder.cache(myCache);
            okHttpClientBuilder.addInterceptor(rewriteRequestInterceptor);
            okHttpClientBuilder.addNetworkInterceptor(REWRITE_RESPONSE_CACHE_CONTROL_INTERCEPTOR);

            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(okHttpClientBuilder.build())
                    .build();

        }
        return retrofit;
    }


    private static Cache getHttpCache() {
        final File cacheDir;
        if (BuildConfig.DEBUG) {
            cacheDir = new File(CachingWithRetrofitApp.getAppContext().getExternalCacheDir(), HTTP_CACHE_DIR);
        } else {
            cacheDir = new File(CachingWithRetrofitApp.getAppContext().getCacheDir(), HTTP_CACHE_DIR);
        }
        return new Cache(cacheDir, HTTP_CACHE_MAX_SIZE);
    }


    private static final Interceptor rewriteRequestInterceptor = new Interceptor() {
        @Override
        public Response intercept(final Chain chain) throws IOException {
            Request request = chain.request();

            if (request.cacheControl().noCache()) {
                return chain.proceed(request);
            }

            if (Utils.isNetworkConnected(CachingWithRetrofitApp.getAppContext())) {
                // the data can be reused for CACHE_DURATION_MIN
                request = request.newBuilder()
                        .cacheControl(new CacheControl.Builder().maxStale(CACHE_DURATION_MIN, TimeUnit.MINUTES).build())
                        .build();
            }
            else {
                // for offline
                request = request.newBuilder()
                        .cacheControl(new CacheControl.Builder().onlyIfCached()
                                .maxStale(STALE_DURATION_DAYS,
                                        TimeUnit.DAYS)
                                .build())
                        .build();
            }

            return chain.proceed(request);
        }
    };



    // this for only response interceptor is invoked for online responses
    private static final Interceptor REWRITE_RESPONSE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Response originalResponse = chain.proceed(chain.request());
            int maxAge = originalResponse.cacheControl().maxAgeSeconds();
            if (maxAge <= 0) {
                return originalResponse.newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader("Expires")
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", String.format(Locale.ENGLISH, "max-age=%d, only-if-cached, max-stale=%d", CACHE_DURATION_SEC, 0))
                        .build();
            } else {
                return originalResponse;
            }
        }
    };
}

5. Create Event listener :

package datanapps.caching.retrofit.network;

import retrofit2.Call;

public interface RetrofitEventListener {
     void onSuccess(Call call, Object response);
     void onError(Call call, Throwable t);
}

 

6. Create User API Interface :

package datanapps.caching.retrofit.network.users;
import java.util.Map;
import datanapps.caching.retrofit.network.users.model.BaseUser;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;

/**
 * API for getting weather from https://darksky.net/
 */
public interface APIUserService {
    @GET("DNARestAPIs/getUserLists")
    Call<BaseUser> getUserList();
}

7. Create user interface service class :

package datanapps.caching.retrofit.network.users;

import java.util.HashMap;
import java.util.Map;

import datanapps.caching.retrofit.network.RetrofitClient;
import datanapps.caching.retrofit.network.RetrofitEventListener;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;


public class ApiUserServiceRepoImpl {

   private static final ApiUserServiceRepoImpl instance = new ApiUserServiceRepoImpl();

    private APIUserService mApiUser;

    public static ApiUserServiceRepoImpl getInstance() {
        return instance;
    }

    /**
     * Invoke getWeather via {@link Call} request.
     * @param retrofitEventListener of RetrofitEventListener.
     */
    public void getWeather(final RetrofitEventListener retrofitEventListener) {
        Retrofit retrofit = RetrofitClient.getRetrofitClient();
        mApiUser = retrofit.create(APIUserService.class);


        Call apiUserCall = mApiUser.getUserList();
       /*
        This is the line which actually sends a network request. Calling enqueue() executes a call asynchronously. It has two callback listeners which will invoked on the main thread
        */
        apiUserCall.enqueue(new Callback<Object>() {
            @Override
            public void onResponse(Call<Object> call, Response<Object> response) {
                /*This is the success callback. Though the response type is JSON, with Retrofit we get the response in the form of WResponse POJO class
                 */
                if (response.body() != null) {
                        retrofitEventListener.onSuccess(call, response.body());
                }
            }
            @Override
            public void onFailure(Call call, Throwable t) {
                /*
                Error callback
                */
                retrofitEventListener.onError(call, t);
            }
        });
    }
}

8. Create Model :  BaseUse.class

package datanapps.caching.retrofit.network.users.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

import java.util.List;

public class BaseUser {

    @SerializedName("userlist")
    @Expose
    private List<User> userlist = null;
    @SerializedName("totalrecords")
    @Expose
    private Integer totalrecords;
    @SerializedName("message")
    @Expose
    private String message;

    public List<User> getUserlist() {
        return userlist;
    }

    public void setUserlist(List<User> userlist) {
        this.userlist = userlist;
    }

    public Integer getTotalrecords() {
        return totalrecords;
    }

    public void setTotalrecords(Integer totalrecords) {
        this.totalrecords = totalrecords;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

-----------------------------------------------------------------------------

User.class

package datanapps.caching.retrofit.network.users.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class User {

    @SerializedName("id")
    @Expose
    private String id;
    @SerializedName("FirstName")
    @Expose
    private String firstName;
    @SerializedName("LastName")
    @Expose
    private String lastName;
    @SerializedName("Email")
    @Expose
    private String email;
    @SerializedName("ContactNo")
    @Expose
    private String contactNo;
    @SerializedName("DOB")
    @Expose
    private String dOB;
    @SerializedName("Image")
    @Expose
    private String image;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getContactNo() {
        return contactNo;
    }

    public void setContactNo(String contactNo) {
        this.contactNo = contactNo;
    }

    public String getDOB() {
        return dOB;
    }

    public void setDOB(String dOB) {
        this.dOB = dOB;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

}

 

8. Call from activity :

 void callUserListApi() {

     if (!Utils.isNetworkConnected(this)) {
         Snackbar.make(recyclerView, "No internet", Snackbar.LENGTH_LONG).show();
     }


    ApiUserServiceRepoImpl.getInstance().getWeather( new RetrofitEventListener() {
        @Override
        public void onSuccess(Call call, Object response) {
            if (response instanceof BaseUser) {
                Log.d("asd", "-----" + ((BaseUser) response).getUserlist().size());
                Snackbar.make(recyclerView, "get data", Snackbar.LENGTH_LONG).show();
                weatherAdapter.setUserList(((BaseUser) response).getUserlist());
                swipeRefreshLayout.setRefreshing(false);
            }
        }
        @Override
        public void onError(Call call, Throwable t) {
            swipeRefreshLayout.setRefreshing(false);
            // snack bar that city can not find
        }
    });
}

 

Please download project and sample here.  CachingWithRetrofit

 

---------------------------------------------

Please suggest ideas for my next blog in comment section.

Thank you so much, Have a great day !

please click LIKE button and SHARE to help others find it!


Click Here To See More

What's Your Reaction?

like
1
dislike
0
love
0
funny
0
angry
0
sad
0
wow
0