Android Paging Library tutorial with Retrofit using MVVM Architecture.

Sunny Sultan
5 min readMar 7, 2019

Last Year Google announced android jetpack components, Paging library is one of them. There are lots of advantages, Suppose you have 5000 images data from a backend API and you do not want load whole 5000 images data at once that time you can use the paging library. This only load small amounts of data from your large data set. It will consume less bandwidth. Also, fewer resources resulting in a smooth app and nice user experience.

So lets Started…

We will parse data from this link https://jsonplaceholder.typicode.com/photos

We will separate page by "albumId"

Include these dependencies on your gradle.build file.

def paging_version = "1.0.1"
implementation "android.arch.paging:runtime:$paging_version"
def lifecycle_version = "1.1.1"

// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version"
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'

implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

First, create a model class. You must put this class under model Package.

public class Photos {
@SerializedName("albumId")
private Integer albumId;
@SerializedName("id")
private Integer id;
@SerializedName("title")
private String title;
@SerializedName("url")
private String url;
@SerializedName("thumbnailUrl")
private String thumbnailUrl;

public Photos(Integer albumId, Integer id, String title, String url, String thumbnailUrl) {
this.albumId = albumId;
this.id = id;
this.title = title;
this.url = url;
this.thumbnailUrl = thumbnailUrl;
}

public Integer getAlbumId() {
return albumId;
}

public void setAlbumId(Integer albumId) {
this.albumId = albumId;
}

public Integer getId() {
return id;
}

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

public String getTitle() {
return title;
}

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

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getThumbnailUrl() {
return thumbnailUrl;
}

public void setThumbnailUrl(String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}
}

Add Retrofit dependency in gradle file.

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

Now Create the Retrofit Instance

public class RetrofitClientInstance {

private static Retrofit retrofit;
private static final String BASE_URL = "https://jsonplaceholder.typicode.com";

public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

Now create an Interface for “GET ”method via Retrofit

public interface GetDataService {

@GET("/photos")
Call<List<Photos>> getAllPhotos(@Query("albumId") long albumId);
}

Now we need to create a PhotoDataSource class under the model package Which will extend PageKeyedDataSource<> Class. Here Three methods should override loadInitial , loadBefore and loadAfter. We need to call retrofit from loadInitial and loadAfter method.

public class PhotoDataSource extends PageKeyedDataSource<Long,Photos> {

GetDataService dataService;

@Override
public void loadInitial(@NonNull LoadInitialParams<Long> params, @NonNull final LoadInitialCallback<Long, Photos> callback) {

dataService = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
Call<List<Photos>> data = dataService.getAllPhotos(1);
data.enqueue(new Callback<List<Photos>>() {
@Override
public void onResponse(Call<List<Photos>> call, Response<List<Photos>> response) {
List<Photos> photosList = response.body();
callback.onResult(photosList,null,(long)2);
}

@Override
public void onFailure(Call<List<Photos>> call, Throwable t) {

}
});

}

@Override
public void loadBefore(@NonNull LoadParams<Long> params, @NonNull LoadCallback<Long, Photos> callback) {

}

@Override
public void loadAfter(@NonNull final LoadParams<Long> params, @NonNull final LoadCallback<Long, Photos> callback) {


dataService = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
Call<List<Photos>> data = dataService.getAllPhotos(params.key);
data.enqueue(new Callback<List<Photos>>() {
@Override
public void onResponse(Call<List<Photos>> call, Response<List<Photos>> response) {
List<Photos> photosList = response.body();
callback.onResult(photosList, String.valueOf(Long.parseLong(paramss.key)+1));
}

@Override
public void onFailure(Call<List<Photos>> call, Throwable t) {

}
});


}
}

Now we need to create a PhotoDataSourceFactory class under the model package which will extends DataSource.Factory class.

public class PhotoDataSourceFactory extends DataSource.Factory {

PhotoDataSource photoDataSource;
MutableLiveData<PhotoDataSource> mutableLiveData;

public PhotoDataSourceFactory() {
mutableLiveData = new MutableLiveData<>();
}

@Override
public DataSource create() {
photoDataSource = new PhotoDataSource();
mutableLiveData.postValue(photoDataSource);
return photoDataSource;
}

public MutableLiveData<PhotoDataSource> getMutableLiveData() {
return mutableLiveData;
}
}

Now need to create a ViewModel class under viewmodel Package and call getMutableLiveData() method from PhotoDataSourceFactory class. Here we need 2 new things. PagedList and Executor. We need to configure PagedList and set Executer for Background threads.

public class MainActivityViewModel extends AndroidViewModel {

PhotoRepository photoRepository;
PhotoDataSourceFactory photoDataSourceFactory;
MutableLiveData<PhotoDataSource> dataSourceMutableLiveData;
Executor executor;
LiveData<PagedList<Photos>> pagedListLiveData;

public MainActivityViewModel(@NonNull Application application) {
super(application);
photoDataSourceFactory = new PhotoDataSourceFactory();
dataSourceMutableLiveData = photoDataSourceFactory.getMutableLiveData();

PagedList.Config config = (new PagedList.Config.Builder())
.setEnablePlaceholders(true)
.setInitialLoadSizeHint(10)
.setPageSize(20)
.setPrefetchDistance(4)
.build();
executor = Executors.newFixedThreadPool(5);
pagedListLiveData = (new LivePagedListBuilder<Long,Photos>(photoDataSourceFactory,config))
.setFetchExecutor(executor)
.build();


}

public LiveData<PagedList<Photos>> getPagedListLiveData() {
return pagedListLiveData;
}
}

We need to modify our model class. We need to add a DiffUtil Callback here. New class should look like this.

public class Photos {
@SerializedName("albumId")
private Integer albumId;
@SerializedName("id")
private Integer id;
@SerializedName("title")
private String title;
@SerializedName("url")
private String url;
@SerializedName("thumbnailUrl")
private String thumbnailUrl;

public Photos(Integer albumId, Integer id, String title, String url, String thumbnailUrl) {
this.albumId = albumId;
this.id = id;
this.title = title;
this.url = url;
this.thumbnailUrl = thumbnailUrl;
}

public Integer getAlbumId() {
return albumId;
}

public void setAlbumId(Integer albumId) {
this.albumId = albumId;
}

public Integer getId() {
return id;
}

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

public String getTitle() {
return title;
}

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

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getThumbnailUrl() {
return thumbnailUrl;
}

public void setThumbnailUrl(String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}

public static final DiffUtil.ItemCallback<Photos> CALLBACK = new DiffUtil.ItemCallback<Photos>() {
@Override
public boolean areItemsTheSame(@NonNull Photos photos, @NonNull Photos t1) {
return photos.id == t1.id;
}

@Override
public boolean areContentsTheSame(@NonNull Photos photos, @NonNull Photos t1) {
return true;
}
};
}

Now create a PagedListAdapter under Adapter Package. I am creating a photoAdapter class and extends PagedListAdapter<>.

public class PhotosAdapter extends PagedListAdapter<Photos,PhotosAdapter.PhotoViewHolder> {

public PhotosAdapter() {
super(Photos.CALLBACK);
}

@NonNull
@Override
public PhotoViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
View view = layoutInflater.inflate(R.layout.list_photoes,viewGroup,false);
return new PhotoViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull PhotoViewHolder photoViewHolder, int i) {

Glide.with(photoViewHolder.itemView.getContext()).load(getItem(i).getUrl()).into(photoViewHolder.ivPhoto);
}


public class PhotoViewHolder extends RecyclerView.ViewHolder {
ImageView ivPhoto;

public PhotoViewHolder(@NonNull View itemView) {
super(itemView);
ivPhoto = itemView.findViewById(R.id.imageView);
}
}
}

Now in MainActivity, we will set adapter with recyclerView. Before set Adapter, we will submitList() our PageList data. We use a LiveData Observer for getting pagedList Data. After getting data and notified in callback we need to set photosAdapter.submitList(photos); before setting Adapter.

public class MainActivity extends AppCompatActivity {

MainActivityViewModel mainActivityViewModel;
@BindView(R.id.recylerview)
RecyclerView photoRecylerview;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mainActivityViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);

photoRecylerview.setLayoutManager(new GridLayoutManager(this,3));

mainActivityViewModel.getPagedListLiveData().observe(this, new Observer<PagedList<Photos>>() {
@Override
public void onChanged(@Nullable PagedList<Photos> photos) {
PhotosAdapter photosAdapter = new PhotosAdapter();
photosAdapter.submitList(photos);
photoRecylerview.setAdapter(photosAdapter);
}
});
}
}

That's it…

Here is the Layout files.

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=".view.MainActivity"
>

<android.support.v7.widget.RecyclerView
android:id="@+id/recylerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</android.support.constraint.ConstraintLayout>

list_photos.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
android:layout_height="wrap_content"
>

<ImageView
android:id="@+id/imageView"
android:layout_width="125sp"
android:layout_height="125sp"
android:layout_weight="1"
tools:srcCompat="@tools:sample/avatars[4]"
/>
</LinearLayout>

Here is my project file Structure.

Project File Structure (MVVM).

--

--