Android

MVVM Design Pattern in Android

At a professional stage of application development, each developer follows a design pattern to write code. The reason to follow the design pattern is to keep the application code simple and easy to understand. Above all, a good design pattern improves the performance of our applications and makes them more efficient, etc.

To write code, there are some different design patterns available like MVC (Model-View-Controller) and MVP (Model_View_Presenter), etc. However, the MVVM (Model-View-ViewModel) design pattern is the best design pattern for most situations.

Above all, Google also recommends this design pattern because of the separation of various components in a project.

  • It separates UI (Activity or Fragment) components from the business logic and business logic from database operations.
  • It’s very easy to understand because everything has a specific place to live.
  • Most importantly, we have a lot less to worry about lifecycle events if it’s done correctly. For example, some previous issues like screen rotation.
MVVM Diagram

MVVM design pattern diagram shows how all the components are interacting with each other. The noticeable thing in this diagram is that each component only depends upon its lower one. Such as, activities and fragments depend on the view model and view model depends upon the repository. The repository is the only class that depends on multiple other classes; persistent data model and a remote backend data source. Such as discussed below,

MVVM Components:

  • Model: Model holds the data of the application and also deals with database operation using Room Persistence. It generally observed by ViewModel through LiveData observers.
  • View:  View represents the UI of the application without any business logic and observes ViewModel with LiveData.
  • ViewModel: ViewModel is responsible for controlling view and model. It contains data-handling business logic to communicate with the model and provides the data to UI. Moreover, implementing changes to model with user interaction.

In this tutorial, we will learn to implement the MVVM design pattern in over application. This application will use room persistence library which uses @annotations to perform database operation in the SQLite Database.

1. Create new Android Studio Project.

Firstly, create a new project with the name Todo List. After that, we will also give the package name.

Create new Project

In this choose project window, there are so many different types of templates for activities. But for now, we will choose the empty activity.

ChooseProject

1.1 colors.xml

After that, add these colors to your colors.xml file

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#d11617</color>
    <color name="colorPrimaryDark">#B31718</color>
    <color name="colorAccent">#D81B60</color>
    <color name="white">#ffffff</color>
</resources>

1.2 build.Gradle (Module: app).

After that, add these dependencies to your build.gradle file

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // ViewModel and LiveData
    def lifecycle_version = "1.1.1"
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    //default library
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    //recycler view, material design library and CardView library
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'com.google.android.material:material:1.2.0-alpha03'
    implementation 'androidx.cardview:cardview:1.0.0'
    //essential room libraries
    implementation 'androidx.room:room-runtime:2.2.3'
    implementation 'org.jetbrains:annotations-java5:15.0'
    annotationProcessor 'androidx.room:room-compiler:2.2.3'
}

2. Creating model Package

After that, right-click on your app package name and create a new package with the name model. This package will contain all classes related to our application data.

create new package
Create new package
package name
Package name

This model package will contain further packages inside it. Those inner packages will be created according to room persistence requirements. Moreover, these packages will be divided into three components like Table EntitiesDao (Data-Access-Object) and Database. These components will be discussed accordingly in the following sections.

2.1 Table Entities.

In this section, we will create a new package by right click on the model package and create a new package with the name entity. Inside the entity package, we will create our model class and name it TodoModel.

TodoModel.java class will have the following items.

  • Some Room @Annotations(@Entity, @PrimaryKey, @ColumnInfo, etc).
  • Getters and setters for all the event fields.
  • Constructor.

TodoModel.java Code.

In conclusion, we have the full code for this activity class.

package com.tutorialscache.todolist.model.entity;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
//@Entity annotation for giving table name and declaring it
@Entity(tableName = "tasks")
public class TodoModel {
    //@primary Key to set id as primary key
    // and making auto increment for each new list
    @PrimaryKey(autoGenerate = true)
    private int id;
    //@ColumnInfo for giving column name todo_title for entity title
    //and this name will be used in all database queries
    @ColumnInfo(name = "todo_title")
    private String title;
    @ColumnInfo(name = "todo_createdAt")
    private String created_at;
    // status is initialized with 0
    @ColumnInfo(name="status_check")
    private int status=0;
    public TodoModel(String title, String created_at) {
        this.title = title;
        this.created_at = created_at;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getCreated_at() {
        return created_at;
    }
    public void setCreated_at(String created_at) {
        this.created_at = created_at;
    }
    public int getStatus() {
        return status;
    }
    public void setStatus(int status) {
        this.status = status;
    }
 @Override
    public String toString() {
        return "TodoModel{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", created_at='" + created_at + '\'' +
                ", status=" + status +
                '}';
    }
}

2.2 Dao (Data-Access-Object).

After that, create Dao which is an interface that defines all the database operations to do on our entity. For that, declare some methods without a body and annotate them with @Insert, @Update@Delete or the generic @Query, where we can pass an SQLite query.
Instead of a Cursor, let these queries return instances of our own Java objects, which we can also wrap into LiveData. As a result, our activity or fragment gets notified as soon as a row in the queried database table changes.

To create a Dao interface, first, create a new package and name it dao. Inside this package, create a new interface class and name it TodoDao.

TodoDao

TodoDao interface will have @Dao annotation on start, which tells the compiler that its a Dao class.

TodoDao.java Code.

In conclusion, we have full code for this interface class.

package com.tutorialscache.todolist.model.dao;
import com.tutorialscache.todolist.model.entity.TodoModel;
import java.util.List;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
//data access object(Dao) to apply all queries on data base
@Dao
public interface TodoDao {
    //annotation for inserting
    @Insert
    void insertTodo(TodoModel todoModel);
    //annotation for deleting
    @Delete
    void deleteTodo(TodoModel todoModel);
    //annotation for deleting
    @Update
    void updateTodo(TodoModel todoModel);
    //delete all list in main activity
    @Query("DELETE FROM tasks WHERE status_check=0")
    void deleteAllTodo();
    //query to get count of pending tasks
    @Query("SELECT COUNT(id) FROM tasks WHERE status_check=0")
    LiveData<Integer> totalPendingTask();
    //query to get count of completed taks
    @Query("SELECT COUNT(id) FROM tasks WHERE status_check=1 ")
    LiveData<Integer> totalCompletedTaks();
    //query get list for Compelete Tasks activity
    @Query("SELECT * FROM tasks WHERE status_check=1 ORDER BY todo_createdAt DESC")
    LiveData<List<TodoModel>> comletedTasks();
    //query to get list for Main Activity
    @Query("SELECT * FROM tasks WHERE status_check=0 ORDER BY todo_createdAt DESC")
    LiveData<List<TodoModel>> getAllNotes();
}

2.3 Database.

After that, create Room Database which is an abstract class that connects the entities to their corresponding Dao. Just as in an SQLiteOpenHelper discussed in the SQLite Database tutorial, we have to define a version number and a migration strategy. With fallbackToDestructiveMigration, we can let Room recreate our database if we increase the version number.
We create our database in the form of a static singleton with the database Builder, where we have to pass our database class and a file name.

To create a Database class, we will first create a new package and name it database. Inside this package, we will create a new abstract class and name it TodoDatabase.

TodoDatabase.java Code.

In conclusion, we have the full code for this database class.

package com.tutorialscache.todolist.model.database;
import android.content.Context;
import com.tutorialscache.todolist.model.dao.TodoDao;
import com.tutorialscache.todolist.model.entity.TodoModel;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
//annotation for getting model class ,setting version and exportSchema
@Database(entities = TodoModel.class, version = 1, exportSchema = false)
public abstract class TodoDatabase extends RoomDatabase {
    //instanse of given class
    private static TodoDatabase instanse;
    
    //define an abstract method of return type TodoDoa
    public abstract TodoDao todoDao();
    
    //method to create instanse of database named todo_list
    public static synchronized TodoDatabase getInstance(Context context)
    { if (instanse==null)
    {
        instanse= Room.databaseBuilder(context.getApplicationContext()
                ,TodoDatabase.class,"todo_list")
                .fallbackToDestructiveMigration().build();
    }
        return instanse;
    }
}

3. Create TodoRepository.java class.

After that, create a Repository which is a simple Java class that adds a layer of abstraction between the data layer and view model. It also mediates between different data sources, like web service and a local cache. Moreover, it hides the different database operations (like SQLite queries) and provides a clean API to the ViewModel.
Since Room doesn’t allow database queries on the main thread. Therefore, we use AsyncTasks to execute them asynchronously. LiveData is fetched on a worker thread automatically, so we don’t have to take care of this.
To create a repository class, right-click on the model package and create a new class with the name TodoRepository.

final model package
final model package

TodoRepository.java Code.

In conclusion, we have the full code for this repository class.

package com.tutorialscache.todolist.model;
import android.app.Application;
import android.os.AsyncTask;
import com.tutorialscache.todolist.model.dao.TodoDao;
import com.tutorialscache.todolist.model.database.TodoDatabase;
import com.tutorialscache.todolist.model.entity.TodoModel;
import java.util.List;
import androidx.lifecycle.LiveData;
public class TodoRepository {
    private TodoDao todoDao;
    private LiveData<List<TodoModel>> todolist,completedTodo;
    private LiveData<Integer> pending,completed;
    // Todo Repository Constructor With Argument Application
    public TodoRepository(Application application)
    {
        TodoDatabase database = TodoDatabase.getInstance(application);
        todoDao = database.todoDao();
        todolist = todoDao.getAllNotes();
        completedTodo= todoDao.comletedTasks();
        pending=todoDao.totalPendingTask();
        completed=todoDao.totalCompletedTaks();
    }
    //our viewmodel will access these mathods
    public void insert(TodoModel todo) {
        new InsertTodoAsyncTask(todoDao).execute(todo);
    }
    public void update(TodoModel todo) {
        new UpdateTodoAsyncTask(todoDao).execute(todo);
    }
    public void delete(TodoModel todo) {
        new DeleteTodoAsyncTask(todoDao).execute(todo);
    }
    public void deleteAllNotes() {
        new DeleteAllTodoAsyncTask(todoDao).execute();
    }
    public LiveData<Integer> getCompleted(){
        return completed;
    }
    public LiveData<Integer> getPending(){
        return pending;
    }
    public LiveData<List<TodoModel>> getAllNotes() {
        return todolist;
    }
    public LiveData<List<TodoModel>> getCompletedTodo(){
        return completedTodo;
    }
    //all asyncronous tasks to run in background thread
    private static class InsertTodoAsyncTask extends AsyncTask<TodoModel, Void, Void> {
        private TodoDao todoDao;
        private InsertTodoAsyncTask(TodoDao todoDao) {
            this.todoDao = todoDao;
        }
        @Override
        protected Void doInBackground(TodoModel... todoModels) {
            todoDao.insertTodo(todoModels[0]);
            return null;
        }
    }
    private static class DeleteTodoAsyncTask extends AsyncTask<TodoModel, Void, Void> {
        private TodoDao todoDao;
        private DeleteTodoAsyncTask(TodoDao todoDao) {
            this.todoDao=todoDao;
        }
        @Override
        protected Void doInBackground(TodoModel... todoModels) {
            todoDao.deleteTodo(todoModels[0]);
            return null;
        }
    }
    private static class UpdateTodoAsyncTask extends AsyncTask<TodoModel, Void, Void> {
        private TodoDao todoDao;
        private UpdateTodoAsyncTask(TodoDao noteDao) {
            this.todoDao = noteDao;
        }
        @Override
        protected Void doInBackground(TodoModel... todoModels) {
            todoDao.updateTodo(todoModels[0]);
            return null;
        }
    }
    private static class DeleteAllTodoAsyncTask extends AsyncTask<Void, Void, Void> {
        private TodoDao todoDao;
        private DeleteAllTodoAsyncTask(TodoDao todoDao) {
            this.todoDao = todoDao;
        }
        @Override
        protected Void doInBackground(Void... voids) {
            todoDao.deleteAllTodo();
            return null;
        }
    }
}

4. Create ViewModel Package.

The ViewModel works as a gateway between the UI controller and the repository. Moreover, It stores and processes data for the activity or fragment and it doesn’t get destroyed on configuration changes. As a result, it doesn’t lose it’s the variable state for example when the device is rotated.
By extending AndroidViewModel, we get a handle to the application context, which we then use to instantiate our RoomDatabase.

To create a view model class, right-click on your app package name and create a new package with the name ViewModel. Inside this package create a new class and name it TodoViewModel.

viewmodel package
ViewModel package

TodoViewModel.java Code.

In conclusion, we have the full code for this view model class.

package com.tutorialscache.todolist.viewmodel;
import android.app.Application;
import com.tutorialscache.todolist.model.TodoRepository;
import com.tutorialscache.todolist.model.entity.TodoModel;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
public class TodoViewModel extends AndroidViewModel {
    private TodoRepository repository;
    private LiveData<List<TodoModel>> todolist,completedTasks;
    private LiveData<Integer> completed,pending;
    //constructor of todoviewModel
    public TodoViewModel(@NonNull Application application)
    {
        super(application);
        repository=new TodoRepository(application);
        todolist=repository.getAllNotes();
        completedTasks=repository.getCompletedTodo();
        completed=repository.getCompleted();
        pending=repository.getPending();
    }
    //our activities will use these methods
    public void insert(TodoModel todoModel)
    {
        repository.insert(todoModel);
    }
    public void delete(TodoModel todoModel)
    {
        repository.delete(todoModel);
    }
    public void update(TodoModel todoModel)
    {
        repository.update(todoModel);
    }
    public void deleteAll()
    {
        repository.deleteAllNotes();
    }
    public  LiveData<Integer> getCompleted(){
        return completed;
    }
    public  LiveData<Integer> getPending(){
        return pending;
    }
    public LiveData<List<TodoModel>> getTodolist()
    {
        return todolist;
    }
    public LiveData<List<TodoModel>>  getCompletedTasks()
    {
        return completedTasks;
    }
}

5. Create a Utils Package.

Right-click on your app package name and create a new package with name utils. Inside this package, create a class and name it Constants.

utils package
utils package

Constants.java Code.

This class contains all constant string variables to be used as key values inside activities.

package com.tutorialscache.todolist.utils;
public class Constants {
    public static final String EXTRA_ID = "EXTRA_ID";
    public static final String EXTRA_TITLE = "EXTRA_TITLE";
    public static final String EXTRA_DATE = "EXTRA_DATE";
    public static final int ADD_TODO = 1;
    public static final int EDIT_TODO = 2;
}

6. Create a View Package.

Firstly, right-click on your app package name and create a new package with name view. Moreover, this package will contain some further packages inside it. Such as, we will create a ui package inside which holds all activities and fragments.

view package
view package

6.1 CreateTodoActivity.java

After that, create a new activity named CreateTodoActivity inside the ui package. which will also create a layout.xml file with it. 

create_todo_activity.xml

In this layout file, add one edit text for title and another one for the date.

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="5dp">
        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/til_todo_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dip">
            <EditText 
                android:id="@+id/et_todo_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textCapWords"
                android:hint="Enter Title"
                android:singleLine="true" />
        </com.google.android.material.textfield.TextInputLayout>
        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/til_todo_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="5dip"
            android:layout_marginTop="5dip"
            android:layout_weight="1">
            <EditText
                android:id="@+id/et_todo_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:clickable="false"
                android:hint="Enter Date" />
        </com.google.android.material.textfield.TextInputLayout>
    </LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

save_menu.xml

After that, right-click on resource directory and create a new menu file save_menu and the following code.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/save_todo"
        android:icon="@drawable/ic_save_white_24dp"
        android:title="Save"
        app:showAsAction="ifRoom" />
</menu>

AndroidManifest.xml

Add the following property ‘android:parentActivityName’ in the activity tag of the manifest file. As a result, when the back home button will be pressed it will send us back to the main activity.

<activity
    android:name=".view.ui.CreateTodoActivity"
    //this tells compiler to go back to TodoActivity on back button
    android:parentActivityName=".view.ui.TodoActivity">
</activity>

CreateTodoActivity.java Code.

In our CreateTodoActivity, we will inflate a custom options menu in onCreateOptionsMenu. Then set our own vector icon as the back button with setHomeAsUpIndicator and confirm the input in onOptionsItemSelected.
Instead of accessing a ViewModel in this activity, we send the input back to the MainActivity and insert the data into the Room database in the onActivityResult method.

package com.tutorialscache.todolist.view.ui;
import androidx.appcompat.app.AppCompatActivity;
import android.app.DatePickerDialog;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Toast;
import com.tutorialscache.todolist.R;
import com.tutorialscache.todolist.utils.Constants;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class CreateTodoActivity extends AppCompatActivity {
    private EditText editTextTitle;
    private EditText editTextTime;
    private static Date eventDate;
    private String startDateStr;
    private Calendar myCalendar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_todo);
        editTextTitle = findViewById(R.id.et_todo_title);
        editTextTime = findViewById(R.id.et_todo_content);
        //set it as current date.
        String date_n = new SimpleDateFormat("dd MMM, yyyy", Locale.getDefault()).format(new 
        Date());
        editTextTime.setText(date_n);
        //to disable editing in date section
        editTextTime.setKeyListener(null);
        editTextTime.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getDate();
            }
        });
        getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back);
        Intent intent = getIntent();
        //condition to check id user sends id form main activity then the title will change 
        accordingly
        if (intent.hasExtra(Constants.EXTRA_ID)) {
            setTitle("Edit Todo");
            editTextTitle.setText(intent.getStringExtra(Constants.EXTRA_TITLE));
            editTextTime.setText(intent.getStringExtra(Constants.EXTRA_DATE));
        } else {
            setTitle("Add Todo");
        }
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.save_menu, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.save_todo:
                saveTodo();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    //method to save date and title
    private void saveTodo() {
        String title = editTextTitle.getText().toString();
        String time = editTextTime.getText().toString();
        //check if time or date id empty
        if (title.trim().isEmpty() || time.trim().isEmpty())
        {
            Toast.makeText(this, "Please insert a title and date",Toast.LENGTH_SHORT).show();
            return;
        }
        Intent data = new Intent();
        data.putExtra(Constants.EXTRA_TITLE, title);
        data.putExtra(Constants.EXTRA_DATE, time);
        int id = getIntent().getIntExtra(Constants.EXTRA_ID, -1);
        if (id != -1) {
            data.putExtra(Constants.EXTRA_ID, id);
        }
        setResult(RESULT_OK,data);
        finish();
    }
    //method to get date
    private void getDate() {
        myCalendar = Calendar.getInstance();
        final DatePickerDialog.OnDateSetListener date =new 
          DatePickerDialog.OnDateSetListener() {
            @Override
            public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth)
            {
                myCalendar.set(Calendar.YEAR, year);
                myCalendar.set(Calendar.MONTH, monthOfYear);
                myCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
                //create a date string
                String myFormat = "dd MMM, yyyy";
                SimpleDateFormat sdf = new SimpleDateFormat(myFormat, Locale.ENGLISH);
                try {
                    eventDate = sdf.parse(sdf.format(myCalendar.getTime()));
                    startDateStr = sdf.format(myCalendar.getTime());
                    editTextTime.setText(startDateStr);
                }
                catch (ParseException e) {
                    e.printStackTrace();
                }
            }
        };
        DatePickerDialog datePickerDialog = new
                DatePickerDialog(CreateTodoActivity.this, date,
                myCalendar.get(Calendar.YEAR),
                myCalendar.get(Calendar.MONTH),
                myCalendar.get(Calendar.DAY_OF_MONTH));
        // Limiting access to past dates in the step below:
        datePickerDialog.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
        datePickerDialog.show();
    }
}
CreateTodoActivity
CreateTodoActivity

6.2 TodoActivity.java

Firstly, rename our MainActivity to TodoActivity. In order to do that, right-click on MainActivity and go to the refactor option to rename it. 

Rename MainActivity.java
Rename MainActivity.java
Rename to TodoActivity.java
Rename to TodoActivity.java

After that, drag it inside ui package. As a result, the ui package will look like

ui package
ui package

activity_todo.xml

After that, add a recycler view, FloatingActionButton and an image view in this XML file. This image view will be used as the default image when the listview will be empty.

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.ui.TodoActivity">
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:id="@+id/rv_todo_list"
        android:layout_height="match_parent">
    </androidx.recyclerview.widget.RecyclerView>
    <ImageView
        android:id="@+id/empty"
        android:src="@drawable/emptytodo"
        android:layout_width="100dp"
        android:layout_marginTop="200dp"
        android:layout_gravity="center_horizontal"
        android:layout_height="140dp" >
    </ImageView>
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab_new_todo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:src="@drawable/ic_add_white_24dp" >
    </com.google.android.material.floatingactionbutton.FloatingActionButton>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

main_menu.xml

After that, right-click on resource directory and create a new menu file main_menu and the following code.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
 <item
        android:id="@+id/deleteall"
        android:title="Delete All"
        android:checked="true" />
</menu>

todo_list.xml

After that, create a new layout resource file and name it as todo_list and use the following code.

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    card:cardCornerRadius="5dp"
    card:cardElevation="5dp"
    card:cardUseCompatPadding="true">
    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="70dp"
        android:padding="10dp">
        <TextView
            android:id="@+id/tv_item_title"
            android:layout_width="190dp"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:maxLines="1"
            android:textColor="@android:color/black" />
        <TextView
            android:id="@+id/tv_item_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_item_title"
            android:layout_marginTop="10dp"
            android:ellipsize="end"
            android:maxLines="1"/>
        <ImageButton
            android:id="@+id/status_checked"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:layout_marginEnd="90dp"
            android:layout_width="40dp"
            android:background="@color/white"
            android:src="@drawable/ic_check_red_800_24dp"
            android:layout_height="match_parent">
        </ImageButton>
        <ImageButton
            android:id="@+id/edit_todo"
            android:layout_width="40dp"
            android:background="@color/white"
            android:src="@drawable/ic_mode_edit_red_800_24dp"
            android:layout_height="match_parent"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="45dp"
            android:layout_centerVertical="true">
        </ImageButton>
        <ImageButton
            android:id="@+id/delete_todoImg"
            android:layout_width="40dp"
            android:background="@color/white"
            android:src="@drawable/ic_delete_red_800_24dp"
            android:layout_height="match_parent"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true">
        </ImageButton>
    </RelativeLayout>
</androidx.cardview.widget.CardView>

TodoListAdapter.java

After that, create an adapter class and name it as TodoListAdapter. This adapter class will be used to display a todo list on our recycler view. Moreover, we will refactor our RecyclerView.Adapter to a ListAdapter. As a result, the list adapter will handle all operations.

Before creating this class, we need to create a new package with the name adapter.

adapter package
adapter package

In conclusion, we have the full code of this adapter class.

package com.tutorialscache.todolist.view.ui.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import com.tutorialscache.todolist.R;
import com.tutorialscache.todolist.model.entity.TodoModel;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
//we will refactor our RecyclerView.Adapter to a ListAdapter ,so now list adapter will handle all operation
public class TodoListAdapter extends ListAdapter<TodoModel,TodoListAdapter.TodoListHolder> {
    private ItemClickListener mListener;
    //Constructor
    public TodoListAdapter() {
        super(DIFF_CALLBACK);
    }
    // AsyncListDiffer to calculate the differences between the old data
    // set and the new one we get passed in the LiveData’s onChanged method
    private static final DiffUtil.ItemCallback<TodoModel> DIFF_CALLBACK = new 
    DiffUtil.ItemCallback<TodoModel>() {
        //to check weather to items have same id or not
        @Override
        public boolean areItemsTheSame(TodoModel oldItem, TodoModel newItem) {
            return oldItem.getId() == newItem.getId();
        }
        //to check weather to items have same contects or not
        @Override
        public boolean areContentsTheSame(TodoModel oldItem, TodoModel newItem) {
            return oldItem.getTitle().equals(newItem.getTitle()) &&
                    oldItem.getCreated_at().equals(newItem.getCreated_at());
        }
    };
    @NonNull
    @Override
    public TodoListHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.todo_list, parent, false);
        return new TodoListHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull TodoListHolder holder, int position) {
        TodoModel currentTodo = getItem(position);
        holder.title.setText(currentTodo.getTitle());
        holder.discription.setText(currentTodo.getCreated_at());
    }
    /**
     * Sets click listener.
     * @param itemClickListener the item click listener
     */
    // allows clicks events to be caught
    public void setClickListener(ItemClickListener itemClickListener) {
        this.mListener = itemClickListener;
    }
    /**
     * The interface Item click listener.
     */
    // parent activity will implement this method to respond to click events
    public interface ItemClickListener {
        void onDeleteItem(TodoModel todoModel);
        void onEditItem(TodoModel todoModel);
        void onCheckItem(TodoModel todoModel);
    }
    public class TodoListHolder extends RecyclerView.ViewHolder 
    implements View.OnClickListener {
        private TextView title;
        private TextView discription;
        ImageButton EditImage, DeleteImage;
        ImageButton statusCheck;
        public TodoListHolder(@NonNull View itemView) {
            super(itemView);
            title = itemView.findViewById(R.id.tv_item_title);
            discription = itemView.findViewById(R.id.tv_item_content);
            EditImage = itemView.findViewById(R.id.edit_todo);
            EditImage.setOnClickListener(this);
            DeleteImage = itemView.findViewById(R.id.delete_todoImg);
            DeleteImage.setOnClickListener(this);
            statusCheck = itemView.findViewById(R.id.status_checked);
            statusCheck.setOnClickListener(this);
        }
        @Override
        public void onClick(View view) {
            int position = getAdapterPosition();
            switch (view.getId())
            {
                case R.id.edit_todo:
                    if (mListener != null)
                        mListener.onEditItem(getItem(position));
                    break;
                case R.id.delete_todoImg:
                    if (mListener!=null)
                        mListener.onDeleteItem(getItem(position));
                    break;
                case R.id.status_checked:
                    if (mListener != null)
                        mListener.onCheckItem(getItem(position));
                    break;
                default:
                    break;
            }
        }
    }
}

TodoActivity.java Code.

Firstly, we will add the LivaData observer in our activity class to show our todo list. After that will add an Override method onActivityResult() to add and insert list item after moving back to this activity.

package com.tutorialscache.todolist.view.ui;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.tutorialscache.todolist.R;
import com.tutorialscache.todolist.model.entity.TodoModel;
import com.tutorialscache.todolist.utils.Constants;
import com.tutorialscache.todolist.view.ui.adapter.TodoListAdapter;
import com.tutorialscache.todolist.viewmodel.TodoViewModel;
import java.util.List;
public class TodoActivity extends AppCompatActivity {
    TodoViewModel todoViewModel;
    RecyclerView recyclerView;
    TodoListAdapter todoListAdapter;
    FloatingActionButton floatingActionButton;
    ImageView emptyImage;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_todo);
        emptyImage=findViewById(R.id.empty);
        //configuring recycler view
        recyclerView = findViewById(R.id.rv_todo_list);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setHasFixedSize(true);
        // setting adapter in recycler view
        todoListAdapter=new TodoListAdapter();
        recyclerView.setAdapter(todoListAdapter);
        //
        floatingActionButton=findViewById(R.id.fab_new_todo);
        floatingActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(TodoActivity.this, CreateTodoActivity.class);
                startActivityForResult(intent, Constants.ADD_TODO);
            }
        });
        //getting access to view model class
        todoViewModel= ViewModelProviders.of(this).get(TodoViewModel.class);
        //add observer to for view model getTodolist()(LiveData)
        todoViewModel.getTodolist().observe(this, new Observer<List<TodoModel>>() {
            @Override
            public void onChanged(List<TodoModel> todoModels) {
                //to show list
                todoListAdapter.submitList(todoModels);
            }
        });
    }
    //override method to show result after inserting list
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //check if request code is for inserting new list then perform insertion
        if (requestCode == Constants.ADD_TODO && resultCode == RESULT_OK) {
            String title = data.getStringExtra(Constants.EXTRA_TITLE);
            String time = data.getStringExtra(Constants.EXTRA_DATE);
           //inserting new list item
            TodoModel todoModel = new TodoModel(title, time);
            todoViewModel.insert(todoModel);
            Toast.makeText(this, "Todo saved", Toast.LENGTH_SHORT).show();
        }
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.main_menu, menu);
        return true;
    }
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        final MenuItem item = menu.findItem(R.id.deleteall);
        //delete all option will be disbled and default image will be appeared if list is 
          empty
        todoViewModel.getPending().observe(TodoActivity.this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                if (integer==0) {
                    item.setEnabled(false);
                    recyclerView.setVisibility(View.GONE);
                    emptyImage.setVisibility(View.VISIBLE);
                }
                else {
                    item.setEnabled(true);
                    recyclerView.setVisibility(View.VISIBLE);
                    emptyImage.setVisibility(View.GONE);
                }
            }
        });
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        if (item.getItemId()==R.id.deleteall)
        {
            //Alert Dialog for deleting all todo
            new AlertDialog.Builder(TodoActivity.this)
                    .setTitle("Delete")
                    .setMessage("Are you sure you want to delete all todo's?")
                    .setPositiveButton("Delete", new DialogInterface.OnClickListener()
                    {
                        public void onClick(DialogInterface dialog, int which) {
                            todoViewModel.deleteAll();
                            Toast.makeText(TodoActivity.this, "All todo's deleted", 
                            Toast.LENGTH_SHORT).show();
                        }
                    })
                    .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).setCancelable(true).show();
        }
        return true;
    }
}
TodoActivity
TodoActivity

Delete TodoList Confirmation Dialog

In order to show confirmation dialog before deleting the list item, add this override method body of interface present in the list adapter.

    //interface method body of list adapter
        todoListAdapter.setClickListener(new TodoListAdapter.ItemClickListener() {
            //method to delete list
            @Override
            public void onDeleteItem( final TodoModel todoModel) {
                //Alert Dialog for deleting all todo
                new AlertDialog.Builder(TodoActivity.this)
                        .setTitle("Delete")
                        .setMessage("Are you sure you want to delete?")
                        .setPositiveButton("Delete", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                // to delete list
                                todoViewModel.delete(todoModel);
                                Log.d("response", "Deleted Item"+todoModel.toString()+"");
                                Toast.makeText(TodoActivity.this, "Todo deleted", 
                                Toast.LENGTH_SHORT).show();
                            }
                        }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() 
                   {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                }).setCancelable(true).show();
            }
    }
delete confirmation dialog
delete confirmation dialog

Updating TodoList.

In order to update the list, add another override method body of interface present in the list adapter for updating the list item.

//interface method body of list adapter
        todoListAdapter.setClickListener(new TodoListAdapter.ItemClickListener() {
            //method to delete list
         @Override
             public void onDeleteItem( final TodoModel todoModel) {
            //add code for delete list item here
         }
         //method to send all list items for edit
         @Override
            public void onEditItem(TodoModel todoModel) {
             Intent intent = new Intent(TodoActivity.this, CreateTodoActivity.class);
             intent.putExtra(Constants.EXTRA_ID, todoModel.getId());
             intent.putExtra(Constants.EXTRA_TITLE, todoModel.getTitle());
             intent.putExtra(Constants.EXTRA_DATE, todoModel.getCreated_at() );
             startActivityForResult(intent, Constants.EDIT_TODO);
            }
        });

This method will carry all list item contents including Id. This Id will be used to uniquely identify that specific list and send it to CreateTodoActivity. In that case, our CreateTodoActivity title will be changed to Edit Todo on receiving Id

Edit Todo
Edit Todo

After that, we will add new data and send the input back to the TodoActivity. In this activity, we will update the data into the Room database in the override method onActivityResult().

  //override method to show result after inserting,updating list
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //check if request code is for inserting new list then perform insertion
        if (requestCode == Constants.ADD_TODO && resultCode == RESULT_OK) {
          //add code to insert list item
        }
        //check if request code is for updating list and perform updation
        else if (requestCode == Constants.EDIT_TODO && resultCode == RESULT_OK)
        {
            int id = data.getIntExtra(Constants.EXTRA_ID, -1);
            if (id == -1)
            {
                Toast.makeText(this, "Todo can't be updated", Toast.LENGTH_SHORT).show();
                return;
            }
            String title = data.getStringExtra(Constants.EXTRA_TITLE);
            String time = data.getStringExtra(Constants.EXTRA_DATE);
            TodoModel todoModel = new TodoModel(title, time);
            todoModel.setId(id);
            todoViewModel.update(todoModel);
            Toast.makeText(this, "todo updated", Toast.LENGTH_SHORT).show();
        }
    }

Check TodoList as Completed.

Our TodoActivity shows list items having (status=0) by default. As a result, our TodoActivity shows a list according to Room query written in Dao Interface and also observes LiveData and shows changes as soon as an event occurs.

    //query to get list for Main Activity
    @Query("SELECT * FROM tasks WHERE status_check=0 ORDER BY todo_createdAt DESC")
    LiveData<List<TodoModel>> getAllNotes();

When we will update status to 1, it will be added automatically to CompletedTodoActivity which will create in the following section. The query to add item in this activity is given below,

   //query get list for Compelete Tasks activity
    @Query("SELECT * FROM tasks WHERE status_check=1 ORDER BY todo_createdAt DESC")
    LiveData<List<TodoModel>> comletedTasks();

In order to check the list as completed, add another override method body of interface present in the list adapter.

//interface method body of list adapter
        todoListAdapter.setClickListener(new TodoListAdapter.ItemClickListener() {
            
            //method to delete list
         @Override
             public void onDeleteItem( final TodoModel todoModel) {
            // write code to delete item here
         }
         
         //method to send all list items for edit
         @Override
            public void onEditItem(TodoModel todoModel) {
             // add code to edit list here
            }
            
            //method to check item completed
            @Override
            public void onCheckItem(final TodoModel todoModel) {
                new AlertDialog.Builder(TodoActivity.this)
                        .setTitle("Task Completed")
                        .setMessage("Are you sure you have completed this todo?")
                        .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                //setting status 1 and updating it
                                todoModel.setStatus(1);
                                todoViewModel.update(todoModel);
                                Log.d("response", "Item Completed"+todoModel.toString()+"");
                                Toast.makeText(TodoActivity.this, "Todo Completed", 
                                Toast.LENGTH_SHORT).show();
                            }
                        })
                        .setNegativeButton("No", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        }).setCancelable(true).show();
         }
        });
Task Completed
Task Completed

TodoActivity.java Final Code.

In conclusion, we have the full code for this activity class.

package com.tutorialscache.todolist.view.ui;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.tutorialscache.todolist.R;
import com.tutorialscache.todolist.model.entity.TodoModel;
import com.tutorialscache.todolist.utils.Constants;
import com.tutorialscache.todolist.view.ui.adapter.TodoListAdapter;
import com.tutorialscache.todolist.viewmodel.TodoViewModel;
import java.util.List;
public class TodoActivity extends AppCompatActivity {
    TodoViewModel todoViewModel;
    RecyclerView recyclerView;
    TodoListAdapter todoListAdapter;
    FloatingActionButton floatingActionButton;
    ImageView emptyImage;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_todo);
        emptyImage=findViewById(R.id.empty);
        //configuring recycler view
        recyclerView = findViewById(R.id.rv_todo_list);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setHasFixedSize(true);
        // setting adapter in recycler view
        todoListAdapter=new TodoListAdapter();
        recyclerView.setAdapter(todoListAdapter);
        
        floatingActionButton=findViewById(R.id.fab_new_todo);
        floatingActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(TodoActivity.this, CreateTodoActivity.class);
                startActivityForResult(intent, Constants.ADD_TODO);
            }
        });
        //getting access to view model class
        todoViewModel= ViewModelProviders.of(this).get(TodoViewModel.class);
        //add observer to for view model getTodolist()(LiveData)
        todoViewModel.getTodolist().observe(this, new Observer<List<TodoModel>>() {
            @Override
            public void onChanged(List<TodoModel> todoModels) {
                //to show list
                todoListAdapter.submitList(todoModels);
            }
        });
        //interface method body of list adapter
        todoListAdapter.setClickListener(new TodoListAdapter.ItemClickListener() {
            //method to delete list
            @Override
            public void onDeleteItem( final TodoModel todoModel) {
                //Alert Dialog for deleting all todo
                new AlertDialog.Builder(TodoActivity.this)
                        .setTitle("Delete")
                        .setMessage("Are you sure you want to delete?")
                        .setPositiveButton("Delete", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                // to delete list
                                todoViewModel.delete(todoModel);
                                Log.d("response", "Deleted Item"+todoModel.toString()+"");
                                Toast.makeText(TodoActivity.this, "Todo deleted", 
                                Toast.LENGTH_SHORT).show();
                            }
                        }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() 
                {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                })
                        .setCancelable(true)
                        .show();
            }
            //method to send all list items for edit
            @Override
            public void onEditItem(TodoModel todoModel) {
                Intent intent = new Intent(TodoActivity.this, CreateTodoActivity.class);
                intent.putExtra(Constants.EXTRA_ID, todoModel.getId());
                intent.putExtra(Constants.EXTRA_TITLE, todoModel.getTitle());
                intent.putExtra(Constants.EXTRA_DATE, todoModel.getCreated_at() );
                startActivityForResult(intent, Constants.EDIT_TODO);
            }
            //method to make item completed
            @Override
            public void onCheckItem(final TodoModel todoModel) {
                new AlertDialog.Builder(TodoActivity.this)
                        .setTitle("Task Completed")
                        .setMessage("Are you sure you have completed this todo?")
                        .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                //setting status 1 and updating it
                                todoModel.setStatus(1);
                                todoViewModel.update(todoModel);
                                Log.d("response", "Item Completed"+todoModel.toString()+"");
                                Toast.makeText(TodoActivity.this, "Todo Completed", 
                                Toast.LENGTH_SHORT).show();
                            }
                        })
                        .setNegativeButton("No", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        }).setCancelable(true).show();
            }
        });
    }
    //override method to show result after inserting,updating list
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //check if request code is for inserting new list then perform insertion
        if (requestCode == Constants.ADD_TODO && resultCode == RESULT_OK) {
            String title = data.getStringExtra(Constants.EXTRA_TITLE);
            String time = data.getStringExtra(Constants.EXTRA_DATE);
            TodoModel todoModel = new TodoModel(title, time);
            todoViewModel.insert(todoModel);
            Toast.makeText(this, "Todo saved", Toast.LENGTH_SHORT).show();
        }
        //check if request code is for updating list and perform updation
        else if (requestCode == Constants.EDIT_TODO && resultCode == RESULT_OK)
        {
            int id = data.getIntExtra(Constants.EXTRA_ID, -1);
            if (id == -1)
            {
                Toast.makeText(this, "Todo can't be updated", Toast.LENGTH_SHORT).show();
                return;
            }
            String title = data.getStringExtra(Constants.EXTRA_TITLE);
            String time = data.getStringExtra(Constants.EXTRA_DATE);
            TodoModel todoModel = new TodoModel(title, time);
            todoModel.setId(id);
            todoViewModel.update(todoModel);
            Toast.makeText(this, "todo updated", Toast.LENGTH_SHORT).show();
        }
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.main_menu, menu);
        return true;
    }
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        final MenuItem item = menu.findItem(R.id.deleteall);
        //deleteall option will be disbled and default image will appear if list will be 
          empty
        todoViewModel.getPending().observe(TodoActivity.this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                if (integer==0) {
                    item.setEnabled(false);
                    recyclerView.setVisibility(View.GONE);
                    emptyImage.setVisibility(View.VISIBLE);
                }
                else {
                    item.setEnabled(true);
                    recyclerView.setVisibility(View.VISIBLE);
                    emptyImage.setVisibility(View.GONE);
                }
            }
        });
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
       if (item.getItemId()==R.id.deleteall)
        {
            //Alert Dialog for deleting all todo
            new AlertDialog.Builder(TodoActivity.this)
                    .setTitle("Delete")
                    .setMessage("Are you sure you want to delete all todo's?")
                    .setPositiveButton("Delete", new DialogInterface.OnClickListener()
                    {
                        public void onClick(DialogInterface dialog, int which) {
                            todoViewModel.deleteAll();
                            Toast.makeText(TodoActivity.this, "All todo's deleted", 
                            Toast.LENGTH_SHORT).show();
                        }
                    })
                    .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).setCancelable(true).show();
        }
       //to move to completed tasks activity
        if (item.getItemId()==R.id.completed)
        {
            Intent intent=new Intent(TodoActivity.this,CompletedTasksActivity.class);
            startActivity(intent);
        }
        return true;
    }
}

6.3 CompletedTodoActivity.java

Firstly, create a new activity named CompletedTodoActivity inside the ui package. This activity will show only the completed todo list.

final ui package
final ui package

activity_completed_todo.xml

In this XML file, add a recycler view and an image view. This image view will be used as the default image when our list view will be empty.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.ui.CompletedTodoActivity">
    <ImageView
        android:id="@+id/empty"
        android:src="@drawable/emptytodo"
        android:layout_width="100dp"
        android:layout_marginTop="200dp"
        android:layout_centerHorizontal="true"
        android:layout_height="140dp" />
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:id="@+id/tv_item_completed"
        android:layout_height="match_parent">
    </androidx.recyclerview.widget.RecyclerView>
</RelativeLayout>

main_menu.xml

After that, add one more item with the title Completed Todo in our main menu file.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/completed"
        android:title="Completed Todo"
        android:checked="true" />
    <item
        android:id="@+id/deleteall"
        android:title="Delete All"
        android:checked="true" />
</menu>

AndroidManifest.xml

After that, add the following property in the activity tag present in the manifest file. As a result, when the back home button will be pressed it will send us back to the main activity.

<activity 
    android:name=".view.ui.CompletedTodoActivity" 
    //this tells compiler to go back to TodoActivity on back button
    android:parentActivityName=".view.ui.TodoActivity">
</activity>

completed_list.xml

After that, add a new layout resource file and name it as completed_list and use the following code.

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    card:cardCornerRadius="5dp"
    card:cardElevation="5dp"
    card:cardUseCompatPadding="true">
    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="70dp"
        android:padding="10dp">
        <TextView
            android:id="@+id/tv_item_title"
            android:layout_width="190dp"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:maxLines="1"
            android:textColor="@android:color/black" />
        <TextView
            android:id="@+id/tv_item_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_item_title"
            android:layout_marginTop="10dp"
            android:ellipsize="end"
            android:maxLines="1"/>
         <ImageButton
            android:id="@+id/undo_todo"
            android:layout_width="40dp"
            android:layout_height="match_parent"
            android:background="@color/white"
            android:src="@drawable/undoicon"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="45dp"
            android:layout_centerVertical="true">
        </ImageButton>
        <ImageButton
            android:id="@+id/delete_todo"
            android:layout_width="40dp"
            android:background="@color/white"
            android:src="@drawable/ic_delete_red_600_24dp"
            android:layout_height="match_parent"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true">
        </ImageButton>
   </RelativeLayout>
</androidx.cardview.widget.CardView>

CompletedListAdapter.java

After that, create a new adapter class and name it as CompletedListAdapter under the adapter package. This class will be used to display a completed todo list in our recycler view.

final adapter package
final adapter package

In conclusion, we have the full code for this adapter class.

package com.tutorialscache.todolist.view.ui.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.tutorialscache.todolist.R;
import com.tutorialscache.todolist.model.entity.TodoModel;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
//we will refactor our RecyclerView.Adapter to a ListAdapter ,so now list adapter will handle all operation
public class CompletedListAdapter extends ListAdapter<TodoModel,CompletedListAdapter.TodoListHolder> {
    private ItemCompletedListener mListener;
    //constructor
    public CompletedListAdapter() {
        super(DIFF_CALLBACK);
    }
    //to check weather to items have same id or not
    private static final DiffUtil.ItemCallback<TodoModel> DIFF_CALLBACK = new 
    DiffUtil.ItemCallback<TodoModel>() {
        @Override
        public boolean areItemsTheSame(TodoModel oldItem, TodoModel newItem) {
            return oldItem.getId() == newItem.getId();
        }
        //to check weather to items have same contects or not
        @Override
        public boolean areContentsTheSame(TodoModel oldItem, TodoModel newItem) {
            return oldItem.getTitle().equals(newItem.getTitle()) &&
                    oldItem.getCreated_at().equals(newItem.getCreated_at());
        }
    };
    @NonNull
    @Override
    public TodoListHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.completed_list, parent, false);
        return new TodoListHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull TodoListHolder holder, int position) {
        TodoModel currentTodo = getItem(position);
        holder.title.setText(currentTodo.getTitle());
        holder.discription.setText(currentTodo.getCreated_at());
    }
    /**
     * Sets click listener.
     * @param itemCompletedListener the item click listener
     */
    // allows clicks events to be caught
    public void setCompletedListner(ItemCompletedListener itemCompletedListener) {
        this.mListener = itemCompletedListener;
    }
    /**
     * The interface Item click listener.
     */
    // parent activity will implement this method to respond to click events
    public interface ItemCompletedListener {
        void onDeleteItem(TodoModel todoModel);
        void onUndoItem(TodoModel todoModel);
    }
    public class TodoListHolder extends RecyclerView.ViewHolder
    implements View.OnClickListener {
        private TextView title;
        private TextView discription;
        ImageButton  DeleteTodo;
        ImageView undoTodo;
        public TodoListHolder(@NonNull View itemView) {
            super(itemView);
            title = itemView.findViewById(R.id.tv_item_title);
            discription = itemView.findViewById(R.id.tv_item_content);
            DeleteTodo = itemView.findViewById(R.id.delete_todo);
            DeleteTodo.setOnClickListener(this);
            undoTodo = itemView.findViewById(R.id.undo_todo);
            undoTodo.setOnClickListener(this);
        }
        @Override
        public void onClick(View view) {
            int position = getAdapterPosition();
            switch (view.getId())
            {
                case R.id.delete_todo:
                    if (mListener!=null)
                        mListener.onDeleteItem(getItem(position));
                    break;
                case R.id.undo_todo:
                    if (mListener != null)
                        mListener.onUndoItem(getItem(position));
                    break;
                default:
                    break;
            }
        }
    }
}

CompletedTodoActivity.java Code.

In conclusion, we have the full code for this activity class. Moreover, this activity class also contains a view model to provide LiveData observers. As a result, the LiveData observer notifies the ViewModel if any change occurs.

package com.tutorialscache.todolist.view.ui;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.tutorialscache.todolist.R;
import com.tutorialscache.todolist.model.entity.TodoModel;
import com.tutorialscache.todolist.view.ui.adapter.CompletedListAdapter;
import com.tutorialscache.todolist.viewmodel.TodoViewModel;
import java.util.List;
public class CompletedTodoActivity extends AppCompatActivity {
    TodoViewModel todoViewModel;
    RecyclerView recyclerView;
    CompletedListAdapter completedListAdapter;
    ImageView emptyImage;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_completed_todo);
        emptyImage=findViewById(R.id.empty);
        //RecyclerVeiw configuration
        recyclerView = findViewById(R.id.tv_item_completed);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setHasFixedSize(true);
        //setting recyclerView to listAdapter
        completedListAdapter=new CompletedListAdapter();
        recyclerView.setAdapter(completedListAdapter);
        //to show enable home back button
        getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back);
        setTitle("Completed Todo");
        //getting access to view model class
        todoViewModel= ViewModelProviders.of(this).get(TodoViewModel.class);
        todoViewModel.getCompletedTasks().observe(this, new Observer<List<TodoModel>>() {
            @Override
            public void onChanged(List<TodoModel> todoModels) {
                //to show list
                completedListAdapter.submitList(todoModels);
            }
        });
        //add observer to for view model getComplete()(LiveData)
        todoViewModel.getCompleted().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                if (integer==0)
                {
                    recyclerView.setVisibility(View.GONE);
                    emptyImage.setVisibility(View.VISIBLE);
                }
                else {
                    recyclerView.setVisibility(View.VISIBLE);
                    emptyImage.setVisibility(View.GONE);
                }
            }
        });
        //interface method body of list adapter
        completedListAdapter.setCompletedListner(new 
        CompletedListAdapter.ItemCompletedListener() {
        //override method to delete list item
            @Override
            public void onDeleteItem(final TodoModel todoModel) {
                //Alert Dialog for deleting all todo
                new AlertDialog.Builder(CompletedTodoActivity.this)
                        .setTitle("Delete")
                        .setMessage("Are you sure you want to delete?")
                        .setPositiveButton("Delete", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                todoViewModel.delete(todoModel);
                                Log.d("response", "Deleted Item"+todoModel.toString()+"");
                                Toast.makeText(CompletedTodoActivity.this, "Todo deleted", 
                                Toast.LENGTH_SHORT).show();
                            }
                        }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() 
                    {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                }).setCancelable(true).show();
            }
            //override method to undo completed list item
            @Override
            public void onUndoItem(final TodoModel todoModel) {
                //Alert Dialog for undo a todo
                new AlertDialog.Builder(CompletedTodoActivity.this)
                        .setTitle("Undo")
                        .setMessage("Are you sure you want to Undo?")
                        .setPositiveButton("Undo", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                todoModel.setStatus(0);
                                todoViewModel.update(todoModel);
                                Log.d("response", "Deleted Item"+todoModel.toString()+"");
                                Toast.makeText(CompletedTodoActivity.this, "Todo deleted", 
                                Toast.LENGTH_SHORT).show();
                            }
                        }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() 
                   {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                }).setCancelable(true).show();
             }
        });
    }
}
Complete Todo
Completed Todo

7. Final Outputs of Application.

In last, we have all activities of this application shown below

All Activities

Comments are closed.