Skip to main content

Network Paging With Paged Key DataSource and REST API

Paging Library is the library provided by Google as part of Architecture Component for using the pagination of the resource. It is helpful to create the pagination from Database using Room as well as with network resource. Here in this article, we will explore the how to use it for Network Paging with backend DataSource.

Why Paging required?



Imagine a scenario where you have thousands of records on your backend which you have to display to the user, but not all data only 10 records. You can't show all the records to the user at once. If you are going to load all the records from backed and display only 10 or 100 records to the user at once.

So why we should load the all data if we just showing only 10 or 100 records at a time. Here the concept of pagination comes into the picture. There are several ways to achieve that but recently Google has released some of the very useful libraries as part fo Architecture Components.

Paging Library is one of that which help to create the mobile app with pagination. In our last article, I have explained how to do the pagination with Room Database now in this article we will learn how to do paging with Network resource.

If you want to go through the same basic component of the Paging Library you can refer the earlier tutorial on Paging.

Network Paging DataSource



DataSource is one of the very important components of the Paging Library. It is responsible for loading the data snapshots of Page into PagedList.

There are many types of the DataSource which we can use depending on our requirement for Network Paging and scenario of the backend. Below is the link to all type of the DataSource you can referer the same to learn the concept of the all these.



In this article, we will implement the PagedKeyedDataSource to retrieve the page using the REST API from a WordPress enable websites.

As explained above we are going to create an app which will implement the PagedKeyedDataSource to load the pages over the network. We will use the Retrofit, ViewModel, and LiveData for our Network Paging Demo app.

So now, Create a project using the android studio and follow the steps given below to create the app.

Dependency Injection to Project



First of all, Kindly add the below dependency into your project level build.gradle files which is required for this project.

// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:1.1.0"
annotationProcessor "android.arch.lifecycle:compiler:1.1.0"
// Paging
implementation "android.arch.paging:runtime:1.0.0-alpha5"
// for REST API
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

implementation 'com.android.support:cardview-v7:26.1.0'
implementation 'com.android.support:recyclerview-v7:26.1.0'
//GSON
implementation 'com.google.code.gson:gson:2.8.1'


Create Model and POJO for Network Paging



Next, we will create our basic model required for our project or we can say simply POJO for our application. In this example, we will just retrieve the heading of all post using the REST.

So create the POJO of the application as given below.

Title.java

package com.nplix.pagedkeydatasourceexample;

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

public class Title {

@SerializedName("rendered")
@Expose
private String rendered;

public String getRendered() {
return rendered;
}

public void setRendered(String rendered) {
this.rendered = rendered;
}

}


Post.java

package com.nplix.pagedkeydatasourceexample;

import android.support.annotation.NonNull;
import android.support.v7.recyclerview.extensions.DiffCallback;

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

public class Post {
@SerializedName("id")
@Expose
private Integer id;

@SerializedName("title")
@Expose
private Title title;

public Integer getId() {
return id;
}

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

public Title getTitle() {
return title;
}

public void setTitle(Title title) {
this.title = title;
}


public Post(final int id, final Title title) {
this.id = id;
this.title = title;
}
//Below DIFF_CALLBACK we will use in our Adapter
public static DiffCallback<Post> DIFF_CALLBACK = new DiffCallback<Post>() {
@Override
public boolean areItemsTheSame(@NonNull Post oldItem, @NonNull Post newItem) {
return oldItem.id == newItem.id;
}

@Override
public boolean areContentsTheSame(@NonNull Post oldItem, @NonNull Post newItem) {
return oldItem.equals(newItem);
}
};
}


Create WP REST Service Using Retrofit



Next, we will create our REST API using Retrofit to retrieve the post from the network. Let's create the WP REST Service and API class as given below.

WPRESTService.java

package com.nplix.pagedkeydatasourceexample;

import java.util.List;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;


public interface WPRESTService {
@GET("wp/v2/posts")
Call<List<Post>> getAllPostByPage(@Query("page") int id);
}


WPRESTAPI.java

package com.nplix.pagedkeydatasourceexample;

import java.util.List;

import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;


public final class WPRestAPI {
private final WPRESTService wordPressService;
static String base_url="https://www.nplix.com/wp-json/";
private WPRestAPI(){
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(base_url)
.addConverterFactory(GsonConverterFactory.create())
.build();
wordPressService=retrofit.create(WPRESTService.class);
}

public static WPRestAPI getInstance(){
return InstanceHolder.INSTASNCE;
}

public final Call<List<Post>> getAllPost(int page){
return wordPressService.getAllPostByPage(page);
}

private static final class InstanceHolder {
private static final WPRestAPI INSTASNCE = new WPRestAPI();
}
}


Create Helper Class for Network API Response



We will now create the helper class for our app to keep the status of our API CALL. It has three status like running, Sucess and fails.

Status.java

package com.nplix.pagedkeydatasourceexample;

public enum Status {
RUNNING,
SUCCESS,
FAILED,
MAX
}


NetworkState.java

package com.nplix.pagedkeydatasourceexample;

class NetworkState {
private final Status status;
private final String msg;

static final NetworkState LOADED;
static final NetworkState LOADING;
static final NetworkState MAXPAGE;

NetworkState(Status status, String msg) {
this.status = status;
this.msg = msg;
}

static {
LOADED=new NetworkState(Status.SUCCESS,"Success");
LOADING=new NetworkState(Status.RUNNING,"Running");
MAXPAGE=new NetworkState(Status.MAX,"No More page");
}

Status getStatus() {
return status;
}

String getMsg() {
return msg;
}

}


Create PagedKeyed DataSource



This is the core class which will responsible for fetching the pages of records from the network. This class as two methods which we will override to full fill the requirement of our app.

First is the loadInitial(): This method is responsible to load the data initially when application launch for the first time, Second is the loadAfter() method it is responsible for the subsequent call to load the data page wise.

So create the DataSource class as given below as given below.

WPDataSource.java


package com.nplix.pagedkeydatasourceexample;

import android.arch.lifecycle.MutableLiveData;
import android.arch.paging.PageKeyedDataSource;
import android.support.annotation.NonNull;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
* Created by PK on 2/16/2018.
*/

public class WPDataSource extends PageKeyedDataSource {

private static String TAG="WP DATA SOURCE";
int page=1;
int max_page;
WPRestAPI wordPressService;
LoadInitialParams initialParams;
// ItemKeyedDataSource.LoadInitialParams initialParams;
LoadParams afterParams;
private MutableLiveData networkState;
private MutableLiveData initialLoading;
private Executor retryExecutor;

public WPDataSource(Executor retryExecutor){
wordPressService=WPRestAPI.getInstance();
networkState=new MutableLiveData();
initialLoading=new MutableLiveData();
this.retryExecutor=retryExecutor;

}
public MutableLiveData getNetworkState() {
return networkState;
}

public MutableLiveData getInitialLoading() {
return initialLoading;
}
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback callback) {
Log.i(TAG, "Loading Page " + 1 + " Load Size " + params.requestedLoadSize);

final List postList =new ArrayList<>();

initialParams=params;
initialLoading.postValue(NetworkState.LOADING);
networkState.postValue(NetworkState.LOADING);
wordPressService.getAllPost(page).enqueue(new Callback<List>() {
@Override
public void onResponse(Call<List> call, Response<List> response) {
if (response.isSuccessful() && response.code() == 200) {
postList.addAll(response.body());
String totalPage=response.headers().get("X-WP-TotalPages");
max_page=Integer.parseInt(totalPage);
Log.d(TAG,"Total page is " + totalPage);
callback.onResult(postList,null,page+1);

initialLoading.postValue(NetworkState.LOADED);
networkState.postValue(NetworkState.LOADED);
initialParams = null;
} else {
Log.e("WP API CALL", response.message());
initialLoading.postValue(new NetworkState(Status.FAILED, response.message()));
networkState.postValue(new NetworkState(Status.FAILED, response.message()));
}
}

@Override
public void onFailure(Call<List> call, Throwable t) {
String errorMessage;
errorMessage = t.getMessage();
if (t == null) {
errorMessage = "error";
}
networkState.postValue(new NetworkState(Status.FAILED, errorMessage));
}
});
}

@Override
public void loadBefore(@NonNull LoadParams params, @NonNull LoadCallback callback) {

}

@Override
public void loadAfter(@NonNull LoadParams params, @NonNull LoadCallback callback) {
Log.i(TAG, "Loading Page " + params.key + " Size " + params.requestedLoadSize);
page=page+1;
final List postList =new ArrayList<>();

afterParams=params;
initialLoading.postValue(NetworkState.LOADING);
networkState.postValue(NetworkState.LOADING);

wordPressService.getAllPost((int) params.key).enqueue(new Callback<List>() {
@Override
public void onResponse(Call<List> call, Response<List> response) {
if (response.isSuccessful() && response.code() == 200) {


postList.addAll(response.body());
// callback.onResult(postList);
callback.onResult(postList, page + 1);
initialLoading.postValue(NetworkState.LOADED);
networkState.postValue(NetworkState.LOADED);
initialParams = null;
}
else if((int)params.key>max_page) {
Log.e("WP API CALL", response.message()+(int)params.key);
initialLoading.postValue(new NetworkState(Status.MAX,response.message()));
networkState.postValue(new NetworkState(Status.MAX, response.message()));
}
else {
Log.e("WP API CALL", response.message());
initialLoading.postValue(new NetworkState(Status.FAILED, response.message()));
networkState.postValue(new NetworkState(Status.FAILED, response.message()));
}
}

@Override
public void onFailure(Call<List> call, Throwable t) {
String errorMessage;
errorMessage = t.getMessage();
if (t == null) {
errorMessage = " error";
}
networkState.postValue(new NetworkState(Status.FAILED, errorMessage));
}
});

}

}


In above class, we are actually calling our rest API from both methods and providing the page as input to WP REST API method and publishing the data using the live data and data source call back using which our Data Source Factory get the updated data, which we are going create below.

DataSource Factory



DataSource Factory is responsible for retrieving the data using the DataSource and Paged list configuration which we will create in later in this article in our ViewModel class.

WPDataSourceFactory.java

package com.nplix.pagedkeydatasourceexample;

import android.arch.lifecycle.MutableLiveData;
import android.arch.paging.DataSource;

import java.util.concurrent.Executor;

/**
* Created by PK on 2/16/2018.
*/

public class WPDataSourceFactory implements DataSource.Factory {
MutableLiveData<WPDataSource> mutableLiveData;
// private WPDataSource wpDataSource;
private WPDataSource wpDataSource;
private Executor executor;
WPDataSourceFactory(Executor executor){
this.executor=executor;
this.mutableLiveData= new MutableLiveData<>();
}

@Override
public DataSource create() {
wpDataSource=new WPDataSource(executor);
mutableLiveData.postValue(wpDataSource);
return wpDataSource;
}
MutableLiveData<WPDataSource> getMutableLiveData() {
return mutableLiveData;
}
}


ViewModel



Now time to create the ViewModel as given below. From the ViewModel, we are setting the PagedList configuration like setInitialLoadPageSizeHint and PageSize for Network Paging Example.

SetInitialLoadPageSize is the hint of the data source to load how many pages as initial and PageSize is the size of the page load in advance.

PostViewModel.java

package com.nplix.pagedkeydatasourceexample;

import android.arch.core.util.Function;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Transformations;
import android.arch.lifecycle.ViewModel;
import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
* Created by PK on 2/11/2018.
*/

public class PostViewModel extends ViewModel {
LiveData<PagedList<Post>> pagedListLiveData;
LiveData<NetworkState> networkState;
Executor executor;
LiveData<WPDataSource> myDataSource;
public PostViewModel(){

executor= Executors.newFixedThreadPool(5);
WPDataSourceFactory wpDataSourceFactory=new WPDataSourceFactory(executor);
myDataSource=wpDataSourceFactory.getMutableLiveData();
networkState= Transformations.switchMap(myDataSource,
(Function<WPDataSource, LiveData<NetworkState>>) WPDataSource::getNetworkState);
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(false)
.setInitialLoadSizeHint(2)
.setPageSize(4).build();

pagedListLiveData = (new LivePagedListBuilder(wpDataSourceFactory, pagedListConfig))
.setBackgroundThreadExecutor(executor)
.build();

}
}


Now we are almost to the final stage of this app so we need to create some additional layout for PostItem and MainActivity.

Create Layout



Our layout files are as given below for our example.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.nplix.pagedkeydatasourceexample.MainActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/postList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>


post_item.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp">

<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textIsSelectable="true"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="visible" />

</android.support.v7.widget.CardView>


network_state_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/error_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<Button
android:visibility="gone"
android:id="@+id/retry_button"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Retry"/>
</LinearLayout>


PagedListAdapter



At last, we have to create an adapter for recycler view by extending the PagedListAdapter. If you want to learn about the PagedListADapter. In Simple, it is a Recycler View Adapter to show the PagedList in the View.

package com.nplix.pagedkeydatasourceexample;

import android.arch.paging.PagedListAdapter;
import android.support.v7.widget.RecyclerView;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class PostAdapter extends PagedListAdapter<Post, RecyclerView.ViewHolder> {

private static final String TAG = "PostAdapter";
private NetworkState networkState;


PostAdapter() {
super(Post.DIFF_CALLBACK);

}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view;

if (viewType == R.layout.post_item) {
view = layoutInflater.inflate(R.layout.post_item, parent, false);
return new PostItemViewHolder(view);
} else if (viewType == R.layout.network_state_item) {
view = layoutInflater.inflate(R.layout.network_state_item, parent, false);
return new NetworkStateItemViewHolder(view);
} else {
throw new IllegalArgumentException("unknown type");
}

}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case R.layout.post_item:
((PostItemViewHolder) holder).bindTo(getItem(position));
break;
case R.layout.network_state_item:
((NetworkStateItemViewHolder) holder).bindView(networkState);
break;
}
}

private boolean hasExtraRow() {
if (networkState != null && networkState != NetworkState.LOADED && networkState!=NetworkState.MAXPAGE) {
return true;
} else {
return false;
}
}

@Override
public int getItemViewType(int position) {
if (hasExtraRow() && position == getItemCount() - 1) {
return R.layout.network_state_item;
} else {
return R.layout.post_item;
}
}

void setNetworkState(NetworkState newNetworkState) {
NetworkState previousState = this.networkState;
boolean previousExtraRow = hasExtraRow();
this.networkState = newNetworkState;
boolean newExtraRow = hasExtraRow();
if (previousExtraRow != newExtraRow) {
if (previousExtraRow) {
notifyItemRemoved(getItemCount());
} else {
notifyItemInserted(getItemCount());
}
} else if (newExtraRow && previousState != newNetworkState) {
notifyItemChanged(getItemCount() - 1);
}
}

static class PostItemViewHolder extends RecyclerView.ViewHolder {
TextView title;

PostItemViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.title);

}

void bindTo(Post post) {
title.setText(Html.fromHtml(post.getTitle().getRendered()));

}
}

static class NetworkStateItemViewHolder extends RecyclerView.ViewHolder {

private final ProgressBar progressBar;
private final TextView errorMsg;
private Button button;

NetworkStateItemViewHolder(View itemView) {
super(itemView);
progressBar = itemView.findViewById(R.id.progress_bar);
errorMsg = itemView.findViewById(R.id.error_msg);
button = itemView.findViewById(R.id.retry_button);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

}
});
}


void bindView(NetworkState networkState) {
if (networkState != null && networkState.getStatus() == Status.RUNNING) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}

if (networkState != null && networkState.getStatus() == Status.FAILED) {
errorMsg.setVisibility(View.VISIBLE);
errorMsg.setText(networkState.getMsg());
}else if (networkState!=null && networkState.getStatus() ==Status.MAX) {
errorMsg.setVisibility(View.VISIBLE);
errorMsg.setText("No More Page to Load");
}
else
{
errorMsg.setVisibility(View.GONE);
}
}
}
}


We have completed our implementation part just one more modification that we need is connecting altogether from Main Activity class.

Required Permission and Main Activity



So Let's modify the Main Activity Class and add required permission to AndroidManifest.xml.

We only required INTERNET permission so we need to add the below code in AndroidManifest.xml file.

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


MainActivity.java

The main part we are doing from our Main Activity is the observing the changes using the ViewModel and its observer and In case of any change in the data of the ViewModel, UI will get updated.

package com.nplix.pagedkeydatasourceexample;

import android.arch.lifecycle.ViewModelProviders;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
private PostViewModel viewModel;
private String TAG = "Main Activity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

RecyclerView recyclerView = findViewById(R.id.postList);
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(llm);

viewModel = ViewModelProviders.of(this).get(PostViewModel.class);

final PostAdapter postAdapter = new PostAdapter();

viewModel.pagedListLiveData.observe(this, pagedList -> {
postAdapter.setList(pagedList);
});

viewModel.networkState.observe(this, networkState -> {
postAdapter.setNetworkState(networkState);
Log.d(TAG, "Network Changed");
});

recyclerView.setAdapter(postAdapter);
}
}


Now we can run and test our app.

Network Paging Example

Conclusion



If we will go step by step then implementing the Paging Library is a very easy task. However, it may need some concentration and time initially for understanding the logic. The core of the above example is the how data follow from DataSource to UI of the App.

 

Comments

Popular posts from this blog

Flutter How to Start Android Activity from Flutter View

Flutter and Dart is an excellent combination for creating the UI, but for accessing the platform-specific service we need to open platform-specific activity. So lets in this article we will explore how to start an android activity and access the service from Flutter View. Create a Project for this Android Activity Flutter View Demo Create a Project From File menu select the New Flutter Project Enter the project name Select the AndroidX support and click on next After the above, we step click on Finish We will have the following project structure created. Create the Second Activity in Android Just go to the android folder and open it in separate windows. We will have the following project structure. Create the Activity Just right-click on the Kotlin folder and create a blank activity from the menu. If you create the activity then you may be required to upgrade the Gradle and do some import. So Just click on update and wait for the project s

Kotlin Parcelable Array Objects Send To Activity

We know that IPC (Inter Process Communication) between the activity is an extremely important part of any application development. We often required that we need to send some data to other activity. For example, we may be required to send an array of data, data could be an Integer, String, Long, Double, Float or any other custom data objects. So, In this example, we are going to learn how to implement the Kotlin Parcelable Array object to send the data from one activity to second activity. What is Parcel? The parcel class is designed as a high-performance IPC transport. A Parcel can contain both flattened data that will be unflattened on the other side of the IPC, and references to live IBinde r objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel. Create Kotlin Parcelable Array Objects Parcelable is API for placing the arbitrary objects into the Parcel. In Actual in android app development, Parcelable is an interface

Create Custom EditText View in Android

We use the EditText for taking the input from the user and use it at several places in our project. We required to do lots of customization for each time and there are lots of redundant code we write. Writing and managing these redundant codes is very difficult for example if we want to change the look and feel of the view them we need to modify it at each place where our EditText is getting used. So to avoid these kinds of the problem we can create our own Custom EditText View by just. The EditText view is just an extension of the TextView with lots of editing option and properties that required for the user input. How To Create Custom EditText View For creating the Custom EditText we need to extend the AppCompatEditText and override all three constructors of the view as given below. import android.content.Context; import android.graphics.Typeface; import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatEditText; import android.util.AttributeSet; public