Hello World, today we are going to see how we can make a todo app using a firebase cloud firestore database. The Firestore database is a very flexible and scalable NoSQL database to make our development very fast. Firestore is very easy to use in android.
FireStore can work smoothly even of your device is offline it can fetch data from the cache in the device so that your app can work offline and update the data when it connects to the internet again. This functionally makes your app more interactive for the users. Firestore database has one more feature: watch realtime changes in the database and show updates in the app.
Firestore database can execute queries so that you can filter your data as per your requirement, this feature makes the Firestore database more likely to choose for your project.
Firestore database stores the data in the documents and these documents are saved in the collection. Think it like the collection is a file folder that has multiple documents and these documents have some data written on it.
Let’s check our app which will be going to build using Firebase Cloud FireStore Database.
Let’s make our app.
The first thing we need to make a new project in the firebase console. Let’s head to https://console.firebase.google.com/ and make a new project by clicking on the new project. Follow all the instructions.
After creating the project let's add our android project. Click on the android icon.
Then, fill your package name then app name.
Download the google-services.json file and paste this file in the app folder of your app.
After that follow the instructions of the firebase. Add required dependencies and hit the sync button.
Your dependencies will look like below.
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0' // recyclerview dependency
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.firebase:firebase-analytics:17.4.4' // firebase required dependency
implementation 'com.google.firebase:firebase-firestore:21.5.0' // firebase firestore dependency
}
We have added dependencies for firestore and recyclerview.
Now let’s move to make layout our app.
To make the layout we will add this code in our activity_main.xml file.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:padding="10sp"
>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/editTextTask"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/addTaskBtn"
android:layout_marginTop="20sp"
android:hint="Add New Task"
android:layout_marginLeft="2sp"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/addTaskBtn"
android:text="add"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/editTextTask"
app:layout_constraintBottom_toBottomOf="@id/editTextTask"
/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="0sp"
android:id="@+id/taskRecyclerView"
app:layout_constraintTop_toBottomOf="@id/editTextTask"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="15sp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
In the above code, we have added an edit text, a button, and a recyclerview.
add below dependency in your app level build.gradle file to use recyclerview in the project.
implementation 'androidx.recyclerview:recyclerview:1.1.0'
If you want to know more about recyclerview you can check it here.
Now we need to make the item layout of the recyclerview.
Make a new layout file and add this code to it.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkBoxTask"
android:text="Task 1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:checked="true"
/>
<ImageView
android:layout_width="35sp"
android:layout_height="35sp"
android:id="@+id/deleteTaskBtn"
android:src="@drawable/ic_delete"
app:layout_constraintTop_toTopOf="@id/checkBoxTask"
app:layout_constraintRight_toRightOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
In the above code, we have a checkbox and an imageview to act as a delete button and set their respective ids.
If you want to add a delete icon in your project you can easily add this by right-clicking on drawable folded then select new and then click on the vector assets and in the next window choose your icon and icon name then save the icon and now you can use this icon in imageview.
After all this, we will add the functionality of our app.
Before making the functionality we need to create a database in the firebase console. Go to your firebase project and click on develop and choose the database and then click on create database.
Note that there will be 2 options. One for Cloud FireStore and in the below on that second option is for the realtime database. But we need to create a database for Cloud FireStore.
Now we need to make a model class (POJO object class) which will be using in the firebase and in the recyclerview.
We need to make a model class that will hold the data. The model class will help to set the data and get the data using getter and setter functions. We have to specify the data type and number of fields that we want to use. See the below code to make a model for the to-do app.
import com.google.firebase.firestore.DocumentId;
public class TaskModel {
@DocumentId
private String taskId;
private String task;
private boolean isTaskDone;
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTask() {
return task;
}
public void setTask(String task) {
this.task = task;
}
public boolean isTaskDone() {
return isTaskDone;
}
public void setTaskDone(boolean taskDone) {
isTaskDone = taskDone;
}
}
In the above model class if you noticed we are using an annotation @DocumentId above the taskId. This annotation is telling that taskId field will be using as document id of the firebase document and when we add data to the firestore database this property will ignore to create in the firestore database. If you are not understanding what I am saying then keep reading you will understand.
Then we make a task string field for holding the task value as a string and the lat field is for either task is done or not. After that, we made the getters and setters method to set the values and get the values.
After making the model we will move to MainActivity.java and make adding data to database functionality.
To add the task data in the firestore we need to get task value from edit text and on the click of the button, we will check the value if it is not empty and then we will use firebase functions to add the data, let’s see how.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText editTextTask = findViewById(R.id.editTextTask);
Button addTaskBtn = findViewById(R.id.addTaskBtn);
addTaskBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String newTask = editTextTask.getText().toString();
if(newTask.isEmpty()){
Toast.makeText(MainActivity.this, "Please add task first.", Toast.LENGTH_SHORT).show();
return;
}
final TaskModel taskModel = new TaskModel();
taskModel.setTaskDone(false);
taskModel.setTask(newTask);
db.collection("todoCollection")
.add(taskModel)
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
@Override
public void onSuccess(DocumentReference documentReference) {
editTextTask.setText(null);
}
});
}
});
}
In the above code, first, we create a database instance of firestore, and then we are getting our EditText and Button from the activity xml layout in the onCreate method.
Then we implement click listener on the button and this will add data in the firestore database. In the click method first, we are getting a string from the edit text and then check if the string is not empty. If the string is empty then we show a toast and then return from the method.
Then we create our task model object and set values using setter methods. We set taskDone to false and set task string. Notice that we are not setting the taskId as it will handle by the Firebase library.
Now, this the point where we will add the data to the database.
First, we get the reference of the collection by using the db.collection method and then call the add method and pass our model which we just created above and then we add the success listener.
In the success listener, we remove the string from the EditText.
Now if you run your app then enter a task in the EditText and click on the add button. Check your database in the Firebase console there will be the newly created document in your collection.
Note that collection is created automatically if it does not exist.
This is how we add the data in the Firestore Database.
Now see how we can get the data from the database and show in the recyclerview.
For showing the data in the recyclerview we need to make an adapter class for the recyclerview as we do while implement recyclerview. So let’s make a new java file and extend it to a recyclerview adapter.
Then we need to pass our viewholder class to do that we create an inner class in our adapter class and pass this class as generic in the adapter and implement the all required methods.
After creating all the classes we need to make a constructor with parameters. These parameters are Context, List of a task model, and db reference.
See the below code for better understanding.
public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder> {
Context context;
List<TaskModel> taskModelList;
FirebaseFirestore db;
public TaskAdapter(Context context, List<TaskModel> taskModelList, FirebaseFirestore db) {
this.context = context;
this.taskModelList = taskModelList;
this.db = db;
}
@NonNull
@Override
public TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.task_item, parent, false);
return new TaskViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TaskViewHolder holder, int position) {
CheckBox task = holder.task;
TaskModel taskModel = taskModelList.get(position);
String taskFromDB = taskModel.getTask();
boolean isTaskDone = taskModel.isTaskDone();
task.setText(taskFromDB);
task.setChecked(isTaskDone);
}
@Override
public int getItemCount() {
return taskModelList.size();
}
class TaskViewHolder extends RecyclerView.ViewHolder{
CheckBox task;
ImageView deleteBtn;
public TaskViewHolder(@NonNull View itemView) {
super(itemView);
task = itemView.findViewById(R.id.checkBoxTask);
deleteBtn = itemView.findViewById(R.id.deleteTaskBtn);
task.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
final int currentPosition = getAdapterPosition();
TaskModel taskModel = taskModelList.get(currentPosition);
String taskId = taskModel.getTaskId();
db.collection("todoCollection")
.document(taskId)
.update("taskDone",isChecked);
}
});
deleteBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final int currentPosition = getAdapterPosition();
TaskModel taskModel = taskModelList.get(currentPosition);
String taskId = taskModel.getTaskId();
db.collection("todoCollection")
.document(taskId).delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
taskModelList.remove(currentPosition);
notifyDataSetChanged();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(context, "Failed to delete the task.", Toast.LENGTH_SHORT).show();
}
})
;
}
});
}
}
}
Return the list size in the getItemCount method. Now come to the viewholder class and write the below code.
class TaskViewHolder extends RecyclerView.ViewHolder{
CheckBox task;
ImageView deleteBtn;
public TaskViewHolder(@NonNull View itemView) {
super(itemView);
task = itemView.findViewById(R.id.checkBoxTask);
deleteBtn = itemView.findViewById(R.id.deleteTaskBtn);
task.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
final int currentPosition = getAdapterPosition();
TaskModel taskModel = taskModelList.get(currentPosition);
String taskId = taskModel.getTaskId();
db.collection("todoCollection")
.document(taskId)
.update("taskDone",isChecked);
}
});
deleteBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final int currentPosition = getAdapterPosition();
TaskModel taskModel = taskModelList.get(currentPosition);
String taskId = taskModel.getTaskId();
db.collection("todoCollection")
.document(taskId).delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
taskModelList.remove(currentPosition);
notifyDataSetChanged();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(context, "Failed to delete the task.", Toast.LENGTH_SHORT).show();
}
})
;
}
});
}
}
If you remember we have created task_item.xml with a checkbox and an imageview for the delete button. We will use these elements in the viewholder and create their references to work with them.
And in the onBindViewHolder method in the adapter. We are setting the checkbox text and its check state.
Now the main thing is to update the existing task value in the database. We want to update the task done value in the database when user check or uncheck the checkbox. We have to add a listener to this in the viewholder.
Inflate our task_item.xml view in onCreateViewHolder.
See also: How to make Night Theme In Android App
task.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
final int currentPosition = getAdapterPosition();
TaskModel taskModel = taskModelList.get(currentPosition);
String taskId = taskModel.getTaskId();
db.collection("todoCollection")
.document(taskId)
.update("taskDone",isChecked);
}
});
First, we get the current item position and then the item’s task id to update the document in the database.
Again we get the reference to the collection then get the document reference by passing the task id in the document method this id is the document id. If you open you collection in the firebase console then the random string are shown are the document ids.
We are using these ids as taskId.
After getting the reference to the document then we are calling the update function and in the first parameter, we pass the field name which we are going to update and the value to be set in the database.
If you run your app and click on the checkbox then the value in the firestore database will update.
Now if the user wants to delete the task so let’s make the delete functionality.
To delete the document from the database. As we did above to update the value we call update method but for deleting the document, we will call the delete method on document reference and add a success listener to remove the task model from the list of task models and update the recyclerview.
deleteBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final int currentPosition = getAdapterPosition();
TaskModel taskModel = taskModelList.get(currentPosition);
String taskId = taskModel.getTaskId();
db.collection("todoCollection")
.document(taskId).delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
taskModelList.remove(currentPosition);
notifyDataSetChanged();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(context, "Failed to delete the task.", Toast.LENGTH_SHORT).show();
}
})
;
}
});
After removing the task we need to notify the adapter to update the recyclerview. So we called to notifyDataSetChanged method.
Now at this point, we have completed the adapter code now attach the adapter to the recyclerview. Go to your activity java file and make an adapter construct with required parameters and set layout manager to the recyclerview and set the adapter to the recyclerview.
Then we fetch all the tasks and convert them to the task model add them to the list of the task model and notify the adapter for change in the list.
db.collection("todoCollection")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if(task.isSuccessful()){
for (QueryDocumentSnapshot document : task.getResult()) {
TaskModel taskModel = document.toObject(TaskModel.class);
taskModelList.add(taskModel);
}
adapter.notifyDataSetChanged();
}else{
Toast.makeText(MainActivity.this, "Error Getting task list.", Toast.LENGTH_SHORT).show();
}
}
});
To convert the document to our model object we will call the document.toObject method and pass the class. This will return object of task and add this object to the list.
After the loop ends, we notify the adapter to change the data.
If you run your app at this point and add a new task then your recyclerview will not show the new task as haven't written that code in on add button click we only added for the database. To add the new task in the recyclerview we need to write the below code in the onClick method.
addTaskBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String newTask = editTextTask.getText().toString();
if(newTask.isEmpty()){
Toast.makeText(MainActivity.this, "Please add task first.", Toast.LENGTH_SHORT).show();
return;
}
final TaskModel taskModel = new TaskModel();
taskModel.setTaskDone(false);
taskModel.setTask(newTask);
db.collection("todoCollection")
.add(taskModel)
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
@Override
public void onSuccess(DocumentReference documentReference) {
String taskId = documentReference.getId();
taskModel.setTaskId(taskId);
taskModelList.add(taskModel);
adapter.notifyDataSetChanged();
editTextTask.setText(null);
}
});
}
});
In the success listener of the firestore, we received the newly created document id which we can use to add a new task in recyclerview. We use the above task model object and add its task id and then pass to the list of task models and finally notify the adapter to update the recyclerview.
Here is the full code of MainActivity.java
public class MainActivity extends AppCompatActivity {
FirebaseFirestore db = FirebaseFirestore.getInstance();
List<TaskModel> taskModelList;
TaskAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText editTextTask = findViewById(R.id.editTextTask);
Button addTaskBtn = findViewById(R.id.addTaskBtn);
taskModelList = new ArrayList<>();
addTaskBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String newTask = editTextTask.getText().toString();
if(newTask.isEmpty()){
Toast.makeText(MainActivity.this, "Please add task first.", Toast.LENGTH_SHORT).show();
return;
}
final TaskModel taskModel = new TaskModel();
taskModel.setTaskDone(false);
taskModel.setTask(newTask);
db.collection("todoCollection")
.add(taskModel)
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
@Override
public void onSuccess(DocumentReference documentReference) {
String taskId = documentReference.getId();
taskModel.setTaskId(taskId);
taskModelList.add(taskModel);
adapter.notifyDataSetChanged();
editTextTask.setText(null);
}
});
}
});
RecyclerView taskRecyclerView = findViewById(R.id.taskRecyclerView);
taskRecyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new TaskAdapter(this, taskModelList, db);
taskRecyclerView.setAdapter(adapter);
db.collection("todoCollection")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if(task.isSuccessful()){
for (QueryDocumentSnapshot document : task.getResult()) {
TaskModel taskModel = document.toObject(TaskModel.class);
taskModelList.add(taskModel);
}
adapter.notifyDataSetChanged();
}else{
Toast.makeText(MainActivity.this, "Error Getting task list.", Toast.LENGTH_SHORT).show();
}
}
});
}
}
Now our app is ready to add a task, delete and update them.
Thanks for reading have a nice day.