A Simple Android ToDo List App with RecyclerView and Firebase Real-time Database

In this android tutorial, we are going to learning how to implement Firebase Real-time database with a simple android ToDo List application.

We have looked at hot to implement Firebase Cloud Messaging but today we will look at using Firebase database.

According to Google Firebase user guide – Store and sync data with our NoSQL cloud database. Data is synced across all clients in realtime, and remains available when your app goes offline. The Firebase Realtime Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. When you build cross-platform apps with our iOS, Android, and JavaScript SDKs, all of your clients share one Realtime Database instance and automatically receive updates with the newest data.

I will suggest you read more about Firebase realtime database in other to understand all the features it offers and how it can reduce you development time.

For the android ToDo app, we are going to use a RecyclerView to list all the task that are to be carried out.

An EditText widget is used to add new task when a button is clicked.

All the task store in Firebase real-time database is retrieved and bind to the RecyclerView through a RecyclerView adapter.

Once a task has been done, the delete icon is used to delete the task from Firebase database.

Before we start, we are going to create a new Firebase project and add Firebase SDK to our android project.

 

Setup Firebase Project

Follow the steps below to register and integrate Firebase SDK to your android project.

Go the Firsbase webite and register if you don’t have an account already. The registration is very simple. After registration, go to the console dashboard and click on create new project button.

In the window modal, enter the name of your project and country as shown in the image below

Android firebase

Click on the create project button to continue. In the console, click the button add Firebase to your android app as shown below

Android firebase

In the next window, add the Package name of your application and click the Add App button as shown below.

Android firebase

Click the continue button to download a Json configuration file which will be added in the root App directory of your project. See the image below

Android firebase

Following the instructions on how to add the Google Services plugin and the Firebase SDK in build.gradle as shown below.

Android firebase

We have successfully added Firebase to our project. We will now focus on our android project.

 

Setup Firebase Database

Go to your Firebase Console and click on the database menu link

Click on the RULES menu tab and change the current rules to the code below

{

“rules”: {

“.read”: true,

“.write”: true

}

}

This is a public access to the database. Please be sure that you read the security and rules  sections of Firebase database. With the above rules, it means that anybody can get access to your database.

Make sure you click on the publish button after the changes. Also note that this rule is use for development and it is not advice to be used for production application.

firebase dashboard

 

Create Android Client Application

Before we dive into more details, it is important for us to understand what we are planning to achieve. Below is the screen-shot of the application we will be creating.

android todo app

Lets start to soil our hands in code. Start up your IDE. For this tutorial, I am using the following tools and environment, feel free to use what works for you.

Windows 10

Android Studio

Sony Xperia ZL

Min SDK 14

Target SDK 23

To create a new android application project, follow the steps as stipulated below.

Go to File menu

Click on New menu

Click on Android Application

Enter Project name: AndroidFirebaseRealtimeDatabase

Package: com.inducesmile.androidfirebaserealtimedatabase

Select Blank Activity

Name your activity : MainActivity

Keep other default selections

Continue to click on next button until Finish button is active, then click on Finish Button.

 

Build.gradle

We are going to complete Firebase integration in our android project by modifying the project build.gradle file.

Open the Project build.gradle file and add a classpath for google services as shown below.

classpath 'com.android.tools.build:gradle:2.0.0'
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.0.0'
        classpath 'com.google.gms:google-services:3.0.0'
    }
}
allprojects {
    repositories {
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

Open the app build.gradle and add Firebase ad dependency and google services plugin as shown below.

apply plugin: 'com.android.application'
android {
    compileSdkVersion 24
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "com.inducesmile.androidfirebaseadmob"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.0.0',
    compile 'com.google.firebase:firebase-database:9.0.2'

}
apply plugin: 'com.google.gms.google-services'

 

Manifest.xml

Since Google is serving the advertisement from their server, it will require network call behind the hood.

We are going to add use permission for internet in Manifest.xml file.

The modify version of Manifest.xml file is shown below

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.inducesmile.androidfirebaseadmob">
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

 

Strings.xml

We are going to update our project strings.xml file located in the values folder inside the res folder. Open the file and add the code below to it.

<resources>
    <string name="app_name">Firebase RealTime Database</string>
    <string name="add_task_button">Add Task</string>
</resources>

 

Colors.xml

Open the colors.xml file in the same location as the strings.xml file and add the code below to the file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
    <color name="colorBlack">#000000</color>
    <color name="colorWhite">#ffffff</color>
    <color name="colorDivider">#ebe9e7</color>
</resources>

 

activity_main.xml

As stated, the main layout file will contain a RecyclerView, EditText and Button controls. Open the activity_main.xml file located in the layout folder and add the code below

<?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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.inducesmile.androidfirebaserealtimedatabase.MainActivity">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/task_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_above="@+id/add_task_wrapper"
        android:layout_marginBottom="12dp"
        android:orientation="vertical"
        android:scrollbars="none"/>
    <LinearLayout
        android:id="@+id/add_task_wrapper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">
        <EditText
            android:id="@+id/add_task_box"
            android:layout_width="0dp"
            android:inputType="text"
            android:layout_height="match_parent"
            android:background="@drawable/bottom_border"
            android:layout_weight="7"/>
        <Button
            android:id="@+id/add_task_button"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:text="@string/add_task_button"
            android:padding="12dp"
            android:layout_margin="0dp"
            android:layout_weight="3"/>
    </LinearLayout>

</RelativeLayout>

 

MainActivity.java

In the MainActivity class, we are going to get the instances of the View controls used in the main layout file.

When the Button widget is click, the text content of the EditText is obtained and send to the Firebase database through Push() and setValue() methods of the DatabaseReference class.

The addChildEventListener of the DatabaseReference class is used to monitor data change in the Firebase database.

Finally, we have created two methods getAllTask(DataSnapshot dataSnapshot) and taskDeletion(DataSnapshot dataSnapshot) that will be called when a new task is added or delete from the Firebase database.

Open the MainActivity class and add the code below.

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private RecyclerView recyclerView;
    private LinearLayoutManager linearLayoutManager;
    private RecyclerViewAdapter recyclerViewAdapter;
    private EditText addTaskBox;
    private DatabaseReference databaseReference;
    private List<Task> allTask;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        allTask = new ArrayList<Task>();
        databaseReference = FirebaseDatabase.getInstance().getReference();
        addTaskBox = (EditText)findViewById(R.id.add_task_box);
        recyclerView = (RecyclerView)findViewById(R.id.task_list);
        linearLayoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        Button addTaskButton = (Button)findViewById(R.id.add_task_button);
        addTaskButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String enteredTask = addTaskBox.getText().toString();
                if(TextUtils.isEmpty(enteredTask)){
                    Toast.makeText(MainActivity.this, "You must enter a task first", Toast.LENGTH_LONG).show();
                    return;
                }
                if(enteredTask.length() < 6){
                    Toast.makeText(MainActivity.this, "Task count must be more than 6", Toast.LENGTH_LONG).show();
                    return;
                }
                Task taskObject = new Task(enteredTask);
                databaseReference.push().setValue(taskObject);
                addTaskBox.setText("");
            }
        });
        databaseReference.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                getAllTask(dataSnapshot);
            }
            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                getAllTask(dataSnapshot);
            }
            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                taskDeletion(dataSnapshot);
            }
            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {
            }
            @Override
            public void onCancelled(DatabaseError databaseError) {
            }
        });
    }
    private void getAllTask(DataSnapshot dataSnapshot){
        for(DataSnapshot singleSnapshot : dataSnapshot.getChildren()){
            String taskTitle = singleSnapshot.getValue(String.class);
            allTask.add(new Task(taskTitle));
            recyclerViewAdapter = new RecyclerViewAdapter(MainActivity.this, allTask);
            recyclerView.setAdapter(recyclerViewAdapter);
        }
    }
    private void taskDeletion(DataSnapshot dataSnapshot){
        for(DataSnapshot singleSnapshot : dataSnapshot.getChildren()) {
            String taskTitle = singleSnapshot.getValue(String.class);
            for(int i = 0; i < allTask.size(); i++){
                if(allTask.get(i).getTask().equals(taskTitle)){
                    allTask.remove(i);
                }
            }
            Log.d(TAG, "Task tile " + taskTitle);
            recyclerViewAdapter.notifyDataSetChanged();
            recyclerViewAdapter = new RecyclerViewAdapter(MainActivity.this, allTask);
            recyclerView.setAdapter(recyclerViewAdapter);
        }
    }
}

 

RecyclerViewAdapter.java

Create a new java file in the project package folder and name it RecyclerViewAdapter.java. Let this class inherits from the RecyclerView.Adapter<RecyclerViewHolders>.

Pass the application context and List of Task data object as parameters. Open the class and add the code below.

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewHolders> {
    private List<Task> task;
    protected Context context;
    public RecyclerViewAdapter(Context context, List<Task> task) {
        this.task = task;
        this.context = context;
    }
    @Override
    public RecyclerViewHolders onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerViewHolders viewHolder = null;
        View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.to_do_list, parent, false);
        viewHolder = new RecyclerViewHolders(layoutView, task);
        return viewHolder;
    }
    @Override
    public void onBindViewHolder(RecyclerViewHolders holder, int position) {
        holder.categoryTitle.setText(task.get(position).getTask());
    }
    @Override
    public int getItemCount() {
        return this.task.size();
    }
}

 

RecyclerViewHolders.java

Follow the same procedure and create another java file. Name this file RecyclerViewHolders.java. Open this file and add the code below to it.

import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;
import java.util.List;
public class RecyclerViewHolders extends RecyclerView.ViewHolder{
    private static final String TAG = RecyclerViewHolders.class.getSimpleName();
    public ImageView markIcon;
    public TextView categoryTitle;
    public ImageView deleteIcon;
    private List<Task> taskObject;
    public RecyclerViewHolders(final View itemView, final List<Task> taskObject) {
        super(itemView);
        this.taskObject = taskObject;
        categoryTitle = (TextView)itemView.findViewById(R.id.task_title);
        markIcon = (ImageView)itemView.findViewById(R.id.task_icon);
        deleteIcon = (ImageView)itemView.findViewById(R.id.task_delete);
        deleteIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(v.getContext(), "Delete icon has been clicked", Toast.LENGTH_LONG).show();
                String taskTitle = taskObject.get(getAdapterPosition()).getTask();
                Log.d(TAG, "Task Title " + taskTitle);
                DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
                Query applesQuery = ref.orderByChild("task").equalTo(taskTitle);
                applesQuery.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for (DataSnapshot appleSnapshot: dataSnapshot.getChildren()) {
                            appleSnapshot.getRef().removeValue();
                        }
                    }
                    @Override
                    public void onCancelled(DatabaseError databaseError) {
                        Log.e(TAG, "onCancelled", databaseError.toException());
                    }
                });
            }
        });
    }
}

 

to_do_list.xml

Create a layout file in the layout folder and name this file to_do_list.xml. Open the layout file and add the code below.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="18dp"
    android:paddingBottom="18dp"
    android:orientation="horizontal">
    <ImageView
        android:id="@+id/task_icon"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:src="@drawable/mark"
        android:layout_weight="15"
        android:contentDescription="@string/app_name" />
    <TextView
        android:id="@+id/task_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="70"
        android:text="@string/app_name"
        android:textSize="16sp"
        android:textColor="@color/colorBlack"/>
    <ImageView
        android:id="@+id/task_delete"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:src="@drawable/delete"
        android:layout_weight="15"
        android:contentDescription="@string/app_name" />
</LinearLayout>

 

Task.java

Create a new java file and name it Task.java. Paste the code below to the file.

public class Task {
    private String task;
    public Task() {}
    public Task(String task) {
        this.task = task;
    }
    public String getTask() {
        return task;
    }
}

This brings us to the end of this tutorial. I hope that you have learn something. Run your app and see for yourself.

You can download the code for this tutorial below. If you are having hard time downloading the tutorial, kindly contact me.

Remember to subscribe with your email address so that you will be among the first to receive my new android blog post once it is published.

Please if you love this tutorial, kindly download my android app – Complete Mathematics – in Google Play Store and let me know what you think about it.

17 Comments

    • Inducesmile
    • Inducesmile
    • Inducesmile
    • Inducesmile
    • Inducesmile

Add a Comment