Android Jetpack Paging Example Using Room

Paging Library

Announced with new additions at Google IO 2018, the Paging Library is a part of Android Jetpack. The Paging Library makes it easier for you to load data gradually and gracefully within your app’s RecyclerView.

The Paging Library helps your app observe and display a reasonable subset of this data. This functionality has several advantages:

  • Data requests consume less network bandwidth and fewer system resources. Users who have metered or small data plans appreciate such data-conscious apps.
  • Even during data updates and refreshes, the app continues to respond quickly to user input.

Get GITHUB code from HERE.

 

 

Major Components

DataSource

DataSource holds the content from the database and external sources such as network APIs and serves as a local storage for PagedLists. A PagedList is generated by LivePagedListBuilder with DataSourceFactory passed into it, optionally with configuration parameters. The PagedList will contain a duplication of the DataSource. This process limits a PagedList to hold only a snapshot of data but allows multiple PagedLists from a single DataSource.

The Room persistence library provides native support for dataSource associated with the Paging library. For a given query, Room allows you to return a DataSource.Factory from the DAO and handles the implementation of the DataSource for you. For example :

@Query("SELECT * FROM COUNTRIES")
public abstract DataSource.Factory<Integer,Country> getCountries();

 

PagedList

PagedList is the key component of the library. This component allows a RecyclerView to load a chunk of data from a DataSource.

PagedList is a collection that loads data in pages, asynchronously. A PagedList can be used to load data from sources you define, and present it easily in your UI with a RecyclerView.

 

PagedListAdapter

PagedListAdapter is a RecyclerView.Adapter that presents paged data from PagedLists in a RecyclerView. PagedListAdapter listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil to compute fine grained updates as new PagedLists are received and then display the list to user in your UI with a recyclerView.

 

1. Adding Components to your Project

Add Architecture Components

To add Architechture Components in your app , open app’s build.gradle and add the following dependencies :

// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:1.0.0"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"

// Room
implementation "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"

// Paging
implementation "android.arch.paging:runtime:1.0.0-alpha4-1"

2. Create DataSource

Create Entity

Represents a modal class whose objects we are going to store in our database. For each entity, a database table is created to hold the items. You must reference the entity class in the Database class.

Country.java

@Entity(tableName = TABLE_NAME)
public class Country {

    public static final String TABLE_NAME = "COUNTRIES";

    public static DiffCallback<Country> DIFF_CALLBACK = new DiffCallback<Country>() {
        @Override
        public boolean areItemsTheSame(@NonNull Country oldItem, @NonNull Country newItem) {
            return oldItem.id == newItem.id;
        }

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


    @PrimaryKey(autoGenerate = true)
    public int id;

    public String countryName;


    @Override
    public String toString() {
        return "Country{" +
                "id=" + id +
                ", countryName='" + countryName + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;

        Country country = (Country) obj;

        return country.id == this.id && country.countryName == this.countryName;
    }
}

 

Create Dao(Data Access Objects)

The DataSource.Factory (implemented by Room) creates the DataSource. Then, LivePagedListBuilder builds the LiveData<PagedList>, using the passed-in DataSource.Factory and PagedList configuration.

 

@Dao
public interface CountryDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertCountriesList(List<Country> countries);

    @Query("SELECT * FROM COUNTRIES")
    public abstract DataSource.Factory<Integer,Country> getCountries();
}

 

Create Database

By calling getCountryDatabase() we get roomDatabase object using which we can get  CountryDao  instance to interact or query with database .

@Database(entities = {Country.class}, version = 1)
public abstract class CountryDatabase extends RoomDatabase {
    private static final String DB_NAME = "Country_Database.db";
    private static CountryDatabase INSTANCE;


    public static CountryDatabase getCountryDatabase(Context context) {
        if (INSTANCE == null) {
            INSTANCE = Room.databaseBuilder(context, CountryDatabase.class, DB_NAME).build();

        }
        return INSTANCE;
    }

    public static void destroyInstance() {
        INSTANCE = null;
    }

    public abstract CountryDao countryDao();
}

3. Create ViewModel

LivePagedListBuilderbuilds the LiveData<PagedList>, using the passed-in DataSource.Factoryand PagedList configuration.

This LivePagedListBuilder object is responsible for creating PagedList objects. When a PagedList is created the LiveData emits the new PagedList to the ViewModel, which in turn passes it to the UI. The UI observes the changed PagedList and uses its PagedListAdapter to update the RecyclerView that presents the PagedList data.

To build and configure a LiveData<PagedList>, use a LivePagedListBuilder. Besides the DataSource.Factory, you need to provide a PagedList configuration, which can include the following options:

  • setPageSize(int) : the size of a page loaded by a PagedList
  • setPrefetchDistance(int) : how far ahead to load
  • setInitialLoadSizeHint(int) : how many items to load when the first load occurs
  • setEnablePlaceholders(boolean) : whether null items to be added to the PagedList, to represent data that hasn’t been loaded yet.

 

public class CountryViewModel extends AndroidViewModel {

    private CountryDatabase countryDatabase;

    public CountryDao countryDao;

    public LiveData<PagedList<Country>> countriesList;

    public CountryViewModel(@NonNull Application application) {
        super(application);

        countryDatabase = CountryDatabase.getCountryDatabase(this.getApplication());

        countryDao = countryDatabase.countryDao();

    }

    public void init(CountryDao countryDao) {
        PagedList.Config pagedListConfig =
                (new PagedList.Config.Builder()).setEnablePlaceholders(true)
                        .setPrefetchDistance(10)
                        .setPageSize(20).build();

        countriesList = (new LivePagedListBuilder(countryDao.getCountries()
                , pagedListConfig))
                .build();


    }

}

 

In the onCreate, we create the instance of ViewModel. In ViewModel class LiveData emits the new PagedList to the ViewModel. which in turn passes it to the UI. The UI observes the changed PagedList and uses its PagedListAdapter to update the RecyclerView that presents the PagedList data.

4. Create Adapter

To bind a PagedList to a RecyclerView, use a PagedListAdapter. The PagedListAdapter gets notified whenever the PagedList content is loaded and then signals the RecyclerView to update.

To tell the PagedListAdapter how to compute the difference between the two elements, you’ll need to implement a new class, DiffCallback .Here, you will define two things as given below :

areItemsTheSame :Checks whether both are reffering to the same item or not.

areContentsTheSame : Checks whether the content of both item are same or not.

public static DiffCallback<Country> DIFF_CALLBACK = new DiffCallback<Country>() {
    @Override
    public boolean areItemsTheSame(@NonNull Country oldItem, @NonNull Country newItem) {
        return oldItem.id == newItem.id;
    }

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

@Override
public boolean equals(Object obj) {
    if (obj == this)
        return true;

    Country country = (Country) obj;

    return country.id == this.id && country.countryName == this.countryName;
}

 

Let’s look at the adapter.So our adapter would extend PagedListAdapter, Here We define the callback, the DIFF_CALLBACK, for our country objects and then  onBindViewHolder() binds the PagedList items to the ViewHolder one by one.

public class CountryAdapter extends PagedListAdapter<Country, CountryAdapter.CountryViewHolder> {

    public CountryAdapter() {
        super(Country.DIFF_CALLBACK);
    }


    @NonNull
    @Override
    public CountryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_country_list, parent, false);

        return new CountryViewHolder(v);

    }

    @Override
    public void onBindViewHolder(@NonNull CountryViewHolder holder, int position) {
        Country country = getItem(position);
        if (country != null) {
            holder.bindTo(country);
        }

    }


    public static class CountryViewHolder extends RecyclerView.ViewHolder {

        TextView countryName;
        TextView uniqueId;


        public CountryViewHolder(View itemView) {
            super(itemView);

            countryName = (TextView) itemView.findViewById(R.id.tv_country);
            uniqueId = (TextView) itemView.findViewById(R.id.tv_id);
        }

        public void bindTo(Country country) {
            countryName.setText(country.countryName);
            uniqueId.setText(Integer.toString(country.id));
        }
    }


}

 

 

Leave a Reply