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 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.
In this choose project window, there are so many different types of templates for activities. But for now, we will choose the empty activity.
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.
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 Entities, Dao (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 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.
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.
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.
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.
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>
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(); } }
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.
After that, drag it inside ui package. As a result, the ui package will look like
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>
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.
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; } }
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(); } }
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.
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(); } });
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.
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>
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.
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(); } }); } }
7. Final Outputs of Application.
In last, we have all activities of this application shown below
Comments are closed.