Data Caching in Android Application like Gmail, What's app

Offline access is one of the basic requirement of Applications now. All developers or Application owner wants their whole app can be usable without internet. Because some time we all face internet connection issue but still we can use some of the application offline like Gmail, What's app and much more. So how they do such things. How you can develop application so it can be used without internet also.

Data Caching can be done with following patterns.
  1. Store data (Server response) in database and access from it.
  2. Store data (Server response) in internal cache directory.
  3. Store data (Server response) in external cache directory.

androprogrammer.com


    In this tutorial i am going to use Reservoir library which allows you to store data in internal cache directory using <Key, Value> pair. There are number of libraries but this is simple and easy to integrate. With Reservoir i have used ION library for Network Request and getting data from server and GSON library for data (Pojo class to response object) mapping. Like ION you can find many networking library like Volley, Retrofit,Smash etc. Volley even supports data caching without any external library but Volley isn't available with Gradle build.

    Steps

    Step - 1
    For learning purpose create a sample app with one activity and give proper name of it. in this tutorial it is DataCachingDemo. In this activity i have used Recycler view to display list of user data. In first step we will setup layout for data. so put below xml code in respective files.

    layout/activity_datacaching_demo.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v4.widget.SwipeRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/swipe_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="?attr/actionBarSize"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context="com.androprogrammer.tutorials.samples.DataCachingDemo">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_userData"
            android:layout_width="match_parent"
            android:layout_margin="5dp"
            android:layout_height="wrap_content"
            android:scrollbars="vertical"/>
    
    </android.support.v4.widget.SwipeRefreshLayout>
    
    

    layout/row_datacaching_item
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v7.widget.CardView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_margin="5dp"
        app:cardCornerRadius="2dp"
        app:cardElevation="@dimen/elevation_normal">
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/white">
    
            <LinearLayout
                android:id="@+id/layout_header"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
    
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_weight="0.3"
                    android:padding="@dimen/padding_small"
                    android:layout_height="90dip">
    
                    <ImageView
                        android:id="@+id/row_userImage"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:scaleType="centerInside"
                        android:src="@mipmap/user_noimage"
                        android:adjustViewBounds="true"
                        android:cropToPadding="false"
                        android:layout_gravity="fill"/>
    
                </LinearLayout>
    
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_weight="0.6"
                    android:layout_height="match_parent"
                    android:orientation="vertical">
    
    
                    <TextView
                        android:id="@+id/row_tv_userName"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:textColor="?android:textColorPrimary"
                        tools:text="@string/hello_world"
                        android:padding="@dimen/padding_small"/>
    
                    <LinearLayout
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:padding="@dimen/padding_small"
                        android:orientation="horizontal">
    
                        <ImageView
                            android:id="@+id/row_icon_mail"
                            android:layout_width="20dip"
                            android:layout_height="20dip"
                            android:paddingLeft="2dp"
                            android:scaleType="fitXY"
                            android:adjustViewBounds="true"
                            tools:tint="?colorAccent"
                            tools:tintMode="add"
                            android:src="@android:drawable/ic_dialog_email"/>
    
    
                        <TextView
                            android:id="@+id/row_tv_email"
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"
                            android:paddingLeft="@dimen/padding_small"
                            android:layout_gravity="center"
                            android:gravity="center_vertical"
                            android:textAppearance="?android:textAppearanceSmall"
                            tools:text="@string/hello_world"/>
    
                    </LinearLayout>
    
                    <LinearLayout
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:padding="@dimen/padding_small"
                        android:orientation="horizontal">
    
                        <ImageView
                            android:id="@+id/row_icon_web"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:src="@mipmap/ic_action_web"
                            android:scaleType="fitXY"
                            android:adjustViewBounds="true"
                            tools:tint="?colorAccent"
                            tools:tintMode="add"/>
    
                        <TextView
                            android:id="@+id/row_userWebsite"
                            android:layout_width="0dp"
                            android:layout_height="match_parent"
                            android:layout_weight="0.8"
                            android:paddingLeft="@dimen/padding_small"
                            android:layout_gravity="center_vertical|center_horizontal"
                            android:gravity="center_vertical"
                            android:textAppearance="?android:textAppearanceSmall"
                            tools:text="@string/hello_world"
                            android:autoLink="web"/>
    
                    </LinearLayout>
    
                </LinearLayout>
    
            </LinearLayout>
    
        </RelativeLayout>
    
    </android.support.v7.widget.CardView>
    

    Step - 2
    In Step - 1 we have completed layout setup to display data in recycler view. and now in this step we are going to get data from the server and then we will store in cache memory and will access from it.
    Note:- When you provide offline data access you have to offer something to refresh data like refresh button in toolbar or can use SwipeRefrshLayout so user can refresh view once internet connection is available.

    I have used generic pattern for different task like to make network request call i have used HttpWebRequest class, To pass data to activity i have used ApiResponse interface and to define constants i have used Common class. Below code is of DataCachingDemo class which we have created to display sample data. so put below code in your main activity.

    sample/DataCachingDemo.java

    package com.androprogrammer.tutorials.samples;
    
    import android.os.Build;
    import android.os.Bundle;
    import android.support.v4.widget.SwipeRefreshLayout;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    import android.transition.Explode;
    import android.transition.Transition;
    import android.view.LayoutInflater;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.Window;
    
    import com.androprogrammer.tutorials.R;
    import com.androprogrammer.tutorials.activities.Baseactivity;
    import com.androprogrammer.tutorials.adapters.DataCachingListAdapter;
    import com.androprogrammer.tutorials.listners.ApiResponse;
    import com.androprogrammer.tutorials.models.UserDataResponse;
    import com.androprogrammer.tutorials.network.HTTPWebRequest;
    import com.androprogrammer.tutorials.util.Common;
    import com.anupcowkur.reservoir.Reservoir;
    import com.anupcowkur.reservoir.ReservoirPutCallback;
    import com.google.gson.GsonBuilder;
    import com.google.gson.JsonArray;
    import com.google.gson.reflect.TypeToken;
    
    import java.lang.reflect.Type;
    import java.util.ArrayList;
    import java.util.List;
    
    public class DataCachingDemo extends Baseactivity implements ApiResponse{
    
     protected View view;
     private RecyclerView recycler_data;
     private SwipeRefreshLayout layout_refresh;
     private RecyclerView.LayoutManager mLayoutManager;
     private List<UserDataResponse> mdata;
     private DataCachingListAdapter mAdapter;
    
     private static final String Cache_Key="userData";
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
      {
       getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
    
       //set the transition
       Transition ts = new Explode();
       ts.setDuration(5000);
       getWindow().setEnterTransition(ts);
       getWindow().setExitTransition(ts);
      }
    
      super.onCreate(savedInstanceState);
    
      setReference();
    
      // base class methods to set toolbar and sub title.
      setToolbarElevation(getResources().getDimension(R.dimen.elevation_normal));
      setToolbarSubTittle(this.getClass().getSimpleName());
      getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    
     }
    
     @Override
     public void setReference() {
      view = LayoutInflater.from(this).inflate(R.layout.activity_datacaching_demo, container);
    
      layout_refresh = (SwipeRefreshLayout) view.findViewById(R.id.swipe_layout);
    
      mLayoutManager = new LinearLayoutManager(DataCachingDemo.this);
    
      recycler_data = (RecyclerView) view.findViewById(R.id.recycler_userData);
      recycler_data.setLayoutManager(mLayoutManager);
    
      mdata = new ArrayList<>();
    
      layout_refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
       @Override
       public void onRefresh() {
        if (!Common.isConnectivityAvailable(DataCachingDemo.this)) {
         if (layout_refresh.isRefreshing()) {
          layout_refresh.setRefreshing(false);
         }
         Common.showToast(DataCachingDemo.this, getResources().getString(R.string.msg_noInternet));
    
        } else
         getDataFromTheServer();
       }
      });
    
      fillAdapterData();
     }
    
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
      // Handle action bar item clicks here. The action bar will
      // automatically handle clicks on the Home/Up button, so long
      // as you specify a parent activity in AndroidManifest.xml.
      switch (item.getItemId()) {
       // Respond to the action bar's Up/Home button
       case android.R.id.home:
        finish();
        break;
      }
    
      return super.onOptionsItemSelected(item);
     }
    
     private void getDataFromTheServer()
     {
      // get data from the server
      HTTPWebRequest.createJsonRequest(DataCachingDemo.this,
                                       Common.App_Network_values.Api_call_getData,
                                       Common.App_Network_values.URL_APP_BASE,
                                       Common.App_Network_values.METHOD_GET,
                                       DataCachingDemo.this);
     }
    
    
     @Override
     public void NetworkRequestCompleted(int apiCode, JsonArray response) {
    
      try {
       switch (apiCode)
       {
        case Common.App_Network_values.Api_call_getData:
    
         if (response != null)
         {
          Type listType = new TypeToken<ArrayList<UserDataResponse>>(){}.getType();
          mdata = new GsonBuilder().create().fromJson(response, listType);
    
          //Put a user data in cache
          Reservoir.putAsync(Cache_Key, mdata, new ReservoirPutCallback() {
           @Override
           public void onSuccess() {
            //success
            Common.showToast(DataCachingDemo.this,"Data caching done...");
    
            fillAdapterData();
           }
    
           @Override
           public void onFailure(Exception e) {
            //error
           }
          });
    
          if (layout_refresh.isRefreshing())
          {
           layout_refresh.setRefreshing(false);
          }
         }
    
         break;
       }
      }catch (Exception e)
      {
       e.printStackTrace();
      }
     }
    
     private void fillAdapterData()
     {
      try {
       // check if data exist or not
       boolean objectExists = Reservoir.contains(Cache_Key);
    
       if (objectExists)
       {
        Type resultType = new TypeToken<List<UserDataResponse>>() {}.getType();
    
        mdata = Reservoir.get(Cache_Key, resultType);
    
        mAdapter = new DataCachingListAdapter(DataCachingDemo.this,mdata);
    
        recycler_data.setAdapter(mAdapter);
       }
       else
       {
        getDataFromTheServer();
       }
      } catch (Exception e) {
       e.printStackTrace();
      }
     }
    
     @Override
     public void networkError(int apiCode, String message) {
      Common.showToast(DataCachingDemo.this,message);
     }
    
     @Override
     public void responseError(int apiCode, String message) {
      Common.showToast(DataCachingDemo.this,message);
     }
    }
    
    


    Step - 3
    In step -3 we are going to setup permission and build file. in above code i have already used comments to describe what it actually do. so please go through it once so you will understand what it actually happening on the basis of different conditions.

    AndroidManifest.xml
    <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"/>
    

    build.gradle
    dependencies {
        compile 'com.google.code.gson:gson:2.3.1'
        compile 'com.koushikdutta.ion:ion:2.1.6'
        compile 'com.anupcowkur:reservoir:2.1'
    }

    That's it from my side. now run the code and check it. if you have problem or query please comment in below comment box.

    Keep coding...

    Source code

    0 comments :

    Post a comment