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.
- Store data (Server response) in database and access from it.
- Store data (Server response) in internal cache directory.
- Store data (Server response) in external cache directory.
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