Architecture Components: Paging Library With Room Database

Architecture Components: Paging Library With Room Database

Paging Library is one of the main parts of the complete set of the Architecture Components Library for MVC app development.  Architecture Components Library includes ViewModel, LiveData, RoomDatabase and Paging Library. In this article, we will create a TODO app that loads the paged data from Database.

Architecture Components Paging Library

Paging Library includes the DataSource, PagedList, PageListBuilder and PagedListAdapter. We will go through each component one by one. But first of all, What is paging Library?

Paging Library is a set of the component which helps app developer to load the data from backed gradually as it required. For example, an app loads thousands of line from the database or network but that all data can’t be display at once to the user. Just a small portion of all those data will be displayed to the user at a time. But if the app goes to load all these data at once then it creates the huge burden on the network and slows down the app as well. If we load the data on load more button click than user have to wait till data is not loaded from the database or network.

Currently, a developer has to use lots of terminologies to efficiently handle all these performance issues and bad user experience. But using the Google Architecture Components Paging Library we can do this work very efficiently without quickly.

What is DataSource in Paging Library?

DataSource is the main component which loads the chunk of pages into the PagedList.  Data loaded by the PagedList can’t be updated but it can grow as required. In case loaded data need to be updated than a new PagedList and DataSource pair is required. But you don’t have to worry about to provide each and every time new set of PagedList and DataSource pair, as its all are auto-managed by architecture components Library.

@Query("SELECT * FROM Todo ORDER BY todo COLLATE NOCASE ASC")
    public abstract DataSource.Factory<Integer, Todo> allTodoByName();

PagedList

PagedList is responsible for query the DataSource and loads the data from DataSource. When we create the PagedList it immediately loads the data from DataSource. Later on when loadAruond() method is called then its load the data from DataSource.

The behaviour of the data loading is controlled by PagedList.Config, using this we can set the page size and prefetch distance.

PagedList.Config pagedListConfig=(new PagedList.Config.Builder()).setEnablePlaceholders(true)
                .setPrefetchDistance(10)
                .setPageSize(20).build();

PagedListAdapter

PagedListAdapter is a RecyclerView.Adapter which is capable of showing the paged data into RecyclerView. It accepts LiveData Paged List as input. For setting the data to this PagedListAdapter we can also user setList(PagedList) method.

public class TodoAdapter extends PagedListAdapter<Todo,TodoViewHolder> {

LivePagedListBuilder

This LivePagedLIstBuilder is used to create the PagedList. We have to provide a config to the constructor of the LivedPagedListBuilder, using this config we can change or customize the behaviour data loading from DataSource.

LiveData<PagedList<Todo>>  todoList = new LivePagedListBuilder<>(
                todoDao.allTodoByName(), pagedListConfig).build();
    }

In above sample, we have provided the list of all TODO item by DAO and PagedList Config.

Description  of the TODO app using Paging Library

We will use the almost all components of the Architecture Components in this TODO app.In this demo app, we will add the todo item and save into the SQLite database and use the sweep to delete option to delete the item. Our ViewModel, LiveData, RoomDatabase and Paging will work together and update the UI automatically in real time.

Paging Library Architecture

 

Create Layout for Todo App

We required two layouts for this demo app. First is the main layout file of the App and second is the item view to show the Todo Item into the RecyclerView.

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.pagingdemo.MainActivity">

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/inputText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="@string/add_todo"
            android:imeOptions="actionDone"
            android:inputType="text"
            android:maxLines="1" />

        <Button
            android:id="@+id/addButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:text="@string/add" />
    </LinearLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/todoList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="vertical"
        android:visibility="visible"
        app:layoutManager="LinearLayoutManager"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout" />

</android.support.constraint.ConstraintLayout>

todo_item.xml

<?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"
                                    android:orientation="vertical"
                                    app:cardUseCompatPadding="true">
    <TextView android:id="@+id/name"
             style="@style/TextAppearance.AppCompat.Medium"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"/>
</android.support.v7.widget.CardView>

Required  Library for Todo App

Below Library is required to create this ToDo app demo. So, add these libraries to your project level build.gradle file.

compile 'com.android.support:cardview-v7:26.1.0'
    compile 'com.android.support:recyclerview-v7:26.1.0'
    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:1.1.0"
    annotationProcessor "android.arch.lifecycle:compiler:1.1.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-alpha5"

Create RoomDatabase Classes for Paging Library Demo

For this demo app, we only required two fields,  Id and name of the todo. Below is the code of our Entity class.

Todo.java

@Entity
public class Todo {
    @PrimaryKey(autoGenerate = true)
    int id;

    public String getTodo() {
        return todo;
    }

    public void setTodo(String todo) {
        this.todo = todo;
    }

    private String todo;
}

Data Access Object for Paging Library

This interface is used for the accessing the data stored in the SQLite database.

TodoDao.java

@Dao
public interface TodoDao {
    @Query("SELECT * FROM Todo ORDER BY todo COLLATE NOCASE ASC")
    public abstract DataSource.Factory<Integer, Todo> allTodoByName();

    @Insert
    public void insert(List<Todo> todos);

    @Insert
    public void insert(Todo todo);

    @Delete
    public void delete(Todo todo);
}

Next, we need our SQLite database class.

TodoDB.java

@Database(entities = Todo.class,version = 1)
public abstract class TodoDB extends RoomDatabase {
    public abstract TodoDao TodoDao();
}

You may have to add the schema details in your compilation script and database migration functionality as well. If not familiar with room database and don’t know how to do then check the RoomDatabase Tutorial.

Create ViewHolder and ViewModel

TodoViewHolder.java

class TodoViewHolder extends RecyclerView.ViewHolder{

    private TextView todoName;
    public  Todo todo;

    public TodoViewHolder(View itemView) {
        super(itemView);
        todoName= itemView.findViewById(R.id.name);
    }
    
    void bindTo(Todo todo){
        this.todo=todo;
        todoName.setText(todo.getTodo());
    }
}

TodoViewModel.java

public class TodoViewModel extends ViewModel {
    
     LiveData<PagedList<Todo>> todoList;
    public TodoViewModel() {
    }

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

        todoList = new LivePagedListBuilder<>(
                todoDao.allTodoByName(), pagedListConfig).build();
    }
}

PagedListAdapter to Load the PagedList.

This is the main component which is used to show the Livedata<PagedList> data into RecyclearView and trigger to load the data from DataSource using PagedList.

TodoAdapter.java

public class TodoAdapter extends PagedListAdapter<Todo,TodoViewHolder> {


    protected TodoAdapter(@NonNull DiffCallback diffCallback) {
        super(diffCallback);
    }


    @Override
    public TodoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        return new TodoViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.todo_item, parent, false));
    }

    @Override
    public void onBindViewHolder(TodoViewHolder holder, int position) {
            holder.bindTo(getItem(position));
    }
    
}

This  PagedListAdapter uses the constructor and takes the DiffUtil callbacks as input. This DiffUtil help RecyclerView to load the latest received PagedList data. Below is the code of the DiffUtil.

private DiffCallback diffCallback = new  DiffCallback<Todo>() {

        @Override
        public boolean areItemsTheSame(@NonNull Todo oldItem, @NonNull Todo newItem) {

            return  oldItem.id == newItem.id;
        }

        @Override
        public boolean areContentsTheSame(@NonNull Todo oldItem, @NonNull Todo newItem) {
            return  oldItem == newItem;
        }
    };

All the above class and code is just helper class for our main class.  Now, we will create the MainActivity.java in which we will bind all these class together.

Create Instance of RoomDatabase

In our main activity class, we will create the instance of the RoomDatabase using Room Database Builder. This RoomDatabase Builder class takes three parameters as shown below.

  • Context is the first parameter
  • Class name of the RoomDatabase in our case its TodoDB.class
  • Third and last is the name of the Database, In our case its MyTodoDB
todoDB= Room.databaseBuilder(getApplicationContext(),TodoDB.class,"MyTodoDB")
                .addCallback(new RoomDatabase.Callback() {
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        super.onCreate(db);
                    }
                }).build();

Get the DAO using database instance.

todoDao=todoDB.TodoDao();

Create the instance of the TodoViewModel using code below.

viewModel = ViewModelProviders.of(this).get(TodoViewModel.class);
        viewModel.init(todoDao);

.To create the LivePagedList using the builder. We have to call viewModel.init(todoDao) method from our main activity.

Create the instance of the PagedListAdapter class using the code below.

TodoAdapter todoAdapter=new TodoAdapter(diffCallback);

Put the observer on the ViewModel todoList to update the UI with latest PagedList.

viewModel.todoList.observe(this, todoAdapter::setList);

Create Method to Sweep to Delete

This method is used for deleting the item from the Database using Dao interface.

public void initSwipeToDelete(){
        new ItemTouchHelper(new ItemTouchHelper.Callback() {

            @Override
            public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                return makeMovementFlags(0,ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
            }

            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }

            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                remove(((TodoViewHolder) viewHolder).todo);
            }
        }).attachToRecyclerView(todoList    );
    }

Create the addTodo method as given below. This method does the following work.

  • Get the ToDo name from editText
  • Insert the todo item into the database using insert() method
  • Then, at last, clear the edit box for next user input
private void addTodo() {
        String newTodo = inputText.getText().toString().trim();
        if (newTodo!=null) {
            insert(newTodo);
            inputText.setText("");
        }
    }

RoomDatabase does not allow you to run DB query on the main thread. So below given insert and remove method is the respective method to delete and remove the ToDo item form SQLite database using a thread executor.

public void  insert(CharSequence text){
        final Todo todo=new Todo();
        todo.setTodo(text.toString());
        IO_EXECUTOR.execute(()->{
            todoDB.TodoDao().insert(todo);
        });
    }

    public void remove(Todo todo){
        IO_EXECUTOR.execute(()->{
            todoDB.TodoDao().delete(todo);
        });

    }

Now, create a method to add the listener on our add Todo button to call the addTodo() method.

private void initAddButtonListener(){
        addBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addTodo();
            }
        });

        inputText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if(actionId== EditorInfo.IME_ACTION_DONE){
                    addTodo();
                }
                return true;
            }
        });

        inputText.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if(event.getAction()== KeyEvent.ACTION_DOWN && keyCode== KeyEvent.KEYCODE_ENTER){
                    addTodo();
                }
                return true;
            }
        });


    }

Conclusion

This Article is very basic or we can say a model for implementing the ViewModel, LiveData, RoomDatabase and Paging Library Architectural components together. If you go step by step then its very easy to implement.

You can download the source code of this Tutorial form NPLIX Repository on GitHub.

Get Email Updates!

Signup now receive an email once I publish new content. I will never give away, trade or sell your email address. You can unsubscribe at any time.

Join 911 other subscribers

This Post Has One Comment

Leave a Reply

Close Menu