ToDo List App with Kotlin and Firebase

In this lesson, we are going to build a simple ToDo app using using Kotlin and one of the most powerful real-time database API Google and Android provides us with – Firebase.

Firebase is a real-time database which provides immense features to manage a mobile app. Actually, not even a mobile app, complex web apps and enterprise applications can make use of Firebase with virtually unlimited performance.

In this lesson we will cover many of Firebase features and its usage, using a simple ToDo app build with Kotlin and powered by Firebase Database API. Let’s get started.

Firebase

Firebase offers many features with its strong API and unlimited performance. We will mention some of these here:

  • No infrastructure needs to be managed by developers or business holders as it is managed by Google itself. Based on needs and access, Firebase can scale automatically.
  • Not just the database API, it offers many solutions like Analytics, Notifications, Ad campaigns and much more. Best thing is, all these products work seamlessly together and allow you to focus on Business needs rather than searching for different APIs to work.
  • There are free plans across all Firebase products. Even with Database, there is a Spark plan which manages your development needs and you do not have to worry about paying anything unless you go live and is ready for a broader audience.
  • Offline? Yes! Firebase works offline. Caching mechanism with Firebase database is just excellent. Firebase Real Time Database SDK persists the app data to disk.

New Project in Android

We will be creating a new project in Android to demonstrate a simple app as a ToDo List app build with Kotlin and using Firebase Database with a complete set of CRUD(Create, Read, Update Delete) operations.

Let us quickly setup a project in Android Studio with name as FirebaseToDo. Our base package will be com.appsdeveloperblog. Remember to check the “Include Kotlin support” while setting up the project.

Adding Firebase support

Next, we add Firebase support in our application. This can be done easily from inside the Android Studio itself. Click Tools > Firebase to open the Assistant window.

Adding Firebase Support to Android Project

Once that is done, the Firebase assistant will appear presenting many Firebase services, we need to select the `Realtime Database` feature:

Firebase RealTime Database

 

Once you click ‘Setup Firebase Realtime Database’, following dialog will appear to confirm setting up the project:

Setup Firebase Realtime Database’

Next, Android Studio will offer to add relevant code snippets to our app like Gradle dependency:

implementation 'com.google.firebase:firebase-database:11.0.4'
...
//Added as last line
apply plugin: 'com.google.gms.google-services'

With other changes, it is clearly shown in the dialog Android Studio presented us:

Adding Real Time Firebase Database
Adding Real Time Firebase Database

Now that the dependencies are set up correctly, we can start to work on our exciting app which will communicate with the Firebase server. Make sure you have created a project in Firebase Console. You will be needing its ID later to be configured in the project too.

Setting Firebase project access rules

This lessons is not meant for authentication purposes. So, the ToDo items we add in our app will be accessible to all the users using the same app as we’re accessing the same database irrespective of the user identity. So, to make our database public, we will add the following script in our Firebase project access rules:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

 

Setting up Firebase Database Access Rules
Setting up Firebase Database Access Rules

In the Data tab above, you will find a URL like:

https://fir-todo-something.firebaseio.com/

Though this URL is not important as our app will access the Firebase over the network and Firebase will identify which database to access via the app’s package name itself and the google-services.json file added automatically by Firebase during its setup.

Add Database collection name

We will add our Database collection name in a new File in Kotlin whose content will be as follows:

package com.appsdeveloperblog.firebasetodo

object Constants {
   @JvmStatic val FIREBASE_ITEM: String = "todo_item"
}

Our collection name will be todo_item. We will see where will this appear as soon as we save first item in Firebase.

As of now, our Database on Firebase looks like:

Adding a To Do Item model

Here, we will design a model object which will be used to hold data for our ToDo list item data. We will intentionally keep this very simple for demonstration purposes.

package com.appsdeveloperblog.firebasetodo

class ToDoItem {

    companion object Factory {
        fun create(): ToDoItem = ToDoItem()
    }

    var objectId: String? = null
    var itemText: String? = null
    var done: Boolean? = false
}

 

Let us understand what we’ve done in this simple model:

  • There are three properties in the model.
    • objectId will hold the Firebase generated ID in the model. This will be used to uniquely identify our item when we show it in a List
    • itemText will contain the ToDo item text
    • done will contain a boolean value, if the listed to-do item is completed or not
  • Before defining three properties, we have also defined companion factory declaration. This makes sure that our model can be instantiated.

Designing the app

Now that the complete configuration is done and we’ve finalised the data model, we can start working on the simple but intuitive design.

We need following functionalities in our app:

  • Have a button to show a dialog where we can enter simple text and submit, so that a new item is added
  • The newly added item should be shown in a listview with a checkbox on each row
  • When this checkbox is clicked, it should mark this item as done
  • When we load data from firebase, it will also tell us if the item is done, in which case, we will just show the checkbox as checked
  • When we long press the list item, we will see a context menu with ‘Update’ and ‘Delete’ options
  • On Update option selected, dialog will again appear with text already filled in and we can update the text and submit it
  • On Delete option selected, the item will be deleted from the app and the Firebase database

To start, we need following elements:

  • A ListView where To Do items can be shown
  • A FloatingActionButton where a user can click and add new To Do item

Our initial UI will look like this:

To make this simple UI, we kept these properties in XML in res > layout > activity_main.xml file as:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.appsdeveloperblog.firebasetodo.MainActivity">

   <ListView
       android:id="@+id/items_list"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:paddingLeft="10dp"
       android:paddingRight="10dp"
       android:scrollbars="none" />

   <android.support.design.widget.FloatingActionButton
       android:id="@+id/fab"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="bottom|end"
       android:layout_margin="15dp"
       android:src="@android:drawable/ic_input_add"
       app:elevation="6dp"
       app:pressedTranslationZ="12dp" />

</android.support.design.widget.CoordinatorLayout>

When this FloatingActionButton is clicked, we need to show a dialog with an EditText where user can enter new item and save it with Database.

Showing a Dialog on FloatingActionButton click

To do this, we must do following steps:

  • Add a click listener on FloatingActionButton
  • Show dialog when FloatingActionButton is clicked

Let’s make changes to our Activity now. We first define a FloatingActionButton in onCreate(…) method which points to the FloatingActionButton in our XML above.

val fab = findViewById<View>(R.id.fab) as FloatingActionButton

Then we set a click listener on it.

//Adding click listener for FAB
fab.setOnClickListener { view ->
   //Show Dialog here to add new Item
   addNewItemDialog()
}

In addNewItemDialog function, we will show a dialog. As of now, our complete onCreate(…) method looks like:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   //reference for FAB
   val fab = findViewById<View>(R.id.fab) as FloatingActionButton
  
   //Adding click listener for FAB
   fab.setOnClickListener { view ->
       //Show Dialog here to add new Item
       addNewItemDialog()
   }
}

Let’s add the method to show the dialog next:

/**
* This method will show a dialog bix where user can enter new item
* to be added
*/
private fun addNewItemDialog() {
   val alert = AlertDialog.Builder(this)

   val itemEditText = EditText(this)
   alert.setMessage("Add New Item")
   alert.setTitle("Enter To Do Item Text")

   alert.setView(itemEditText)

   alert.setPositiveButton("Submit") { dialog, positiveButton ->   }

   alert.show()
}

Once we add the above code, alert dialog will appear once we hit the FloatingActionButton:

Todo list app example in Kotlin

Creating entry in Firebase

This is where our real journey starts. In this section, we will start inserting data into the Firebase. We have already shown the current state of the database. Now, let’s modify the code of positive button listener of the dialog as:

alert.setPositiveButton("Submit") { dialog, positiveButton ->

   val todoItem = ToDoItem.create()
   todoItem.itemText = itemEditText.text.toString()
   todoItem.done = false

   //We first make a push so that a new item is made with a unique ID
   val newItem = mDatabase.child(Constants.FIREBASE_ITEM).push()
   todoItem.objectId = newItem.key

   //then, we used the reference to set the value on that ID
   newItem.setValue(todoItem)

   dialog.dismiss()
   Toast.makeText(this, "Item saved with ID " + todoItem.objectId, Toast.LENGTH_SHORT).show()
}

Let us see what we did above:

  • Created a new todoItem instance and initialised it with default values.
  • Using push() method, we obtain a new ID from firebase which we can provide to our To Do item.
  • Once we do setValue(…), the todoItem will be saved in firebase database.

We’ve used mDatabase in above code. What is it?

mDatabase is the Firebase database reference. Let’s create it as a Global variable in our Activity.

//Get Access to Firebase database, no need of any URL, Firebase
//identifies the connection via the package name of the app
lateinit var mDatabase: DatabaseReference

And initialise it in our onCreate(…) method:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
   ...

   mDatabase = FirebaseDatabase.getInstance().reference
...
}

As simple as that. Now, run the app once again, enter some value and hit ‘Submit’. The item will appear in the Firebase Database as:

Note here that our database name is the top element. Next element is our collection named as todo_item. Followed by it are the list of To Do items.

Reading Firebase data in a List

What is the use of the data until and unless we’re able to read the data. In the previous section, we designed a simple UI with a ListView. Now, we will enhance it with custom UI for its row.

Make a new file in res > layout folder and name it as row_items.xml. Fill it with following design:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:orientation="horizontal"
   android:padding="10dp">

   <CheckBox
       android:id="@+id/cb_item_is_done"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content" />

   <TextView
       android:id="@+id/tv_item_text"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_centerVertical="true"
       android:layout_toEndOf="@+id/cb_item_is_done"
       android:layout_toRightOf="@+id/cb_item_is_done"
       android:text="Item Text"
       android:textColor="#000000"
       android:textSize="20sp" />

   <ImageButton
       android:id="@+id/iv_cross"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignBottom="@+id/tv_item_text"
       android:layout_alignParentEnd="true"
       android:layout_alignParentRight="true"
       android:layout_centerVertical="true"
       android:background="@android:color/transparent"
       android:gravity="end"
       app:srcCompat="@android:drawable/btn_dialog" />

</RelativeLayout>

This is a simple UI which will create following row design:

Clearly, this row is enough to update item state as done using the CheckBox and to delete the row via the ImageButton.

To populate the ListView, we must create a new Adapter which will complete this responsibility.

package com.appsdeveloperblog.firebasetodo

import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.CheckBox
import android.widget.ImageButton
import android.widget.TextView

class ToDoItemAdapter(context: Context, toDoItemList: MutableList<ToDoItem>) : BaseAdapter() {

   private val mInflater: LayoutInflater = LayoutInflater.from(context)
   private var itemList = toDoItemList

   override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {

       val objectId: String = itemList.get(position).objectId as String
       val itemText: String = itemList.get(position).itemText as String
       val done: Boolean = itemList.get(position).done as Boolean

       val view: View
       val vh: ListRowHolder
       if (convertView == null) {
           view = mInflater.inflate(R.layout.row_items, parent, false)
           vh = ListRowHolder(view)
           view.tag = vh
       } else {
           view = convertView
           vh = view.tag as ListRowHolder
       }

       vh.label.text = itemText
       vh.isDone.isChecked = done

       return view
   }

   override fun getItem(index: Int): Any {
       return itemList.get(index)
   }

   override fun getItemId(index: Int): Long {
       return index.toLong()
   }

   override fun getCount(): Int {
       return itemList.size
   }

   private class ListRowHolder(row: View?) {
       val label: TextView = row!!.findViewById<TextView>(R.id.tv_item_text) as TextView
       val isDone: CheckBox = row!!.findViewById<CheckBox>(R.id.cb_item_is_done) as CheckBox
       val ibDeleteObject: ImageButton = row!!.findViewById<ImageButton>(R.id.iv_cross) as ImageButton
   }
}

In above adapter, we:

  • Defined a ListRowHolder to contain items for our UI
  • Populated list items with appropriate data

Now, we must use this Adapter in our Activity. Also, we also have to fetch data from Firebase.

Fetching data from Firebase

Getting data from Firebase is actually easy. Just do this in onCreate(…) method:

mDatabase.orderByKey().addListenerForSingleValueEvent(itemListener)

We will add item listener next. We just add a listener for current database which will fetch and alert the function when the data has been fetched.

We first define some global variables, as:

var toDoItemList: MutableList<ToDoItem>? = null
lateinit var adapter: ToDoItemAdapter
private var listViewItems: ListView? = null

We will initialise each of them in our onCreate(…) method. Once all this is done, our method will look like:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   //reference for FAB
   val fab = findViewById<View>(R.id.fab) as FloatingActionButton
   listViewItems = findViewById<View>(R.id.items_list) as ListView

   //Adding click listener for FAB
   fab.setOnClickListener { view ->
       //Show Dialog here to add new Item
       addNewItemDialog()
   }

   mDatabase = FirebaseDatabase.getInstance().reference
   toDoItemList = mutableListOf<ToDoItem>()
   adapter = ToDoItemAdapter(this, toDoItemList!!)
   listViewItems!!.setAdapter(adapter)
   mDatabase.orderByKey().addListenerForSingleValueEvent(itemListener)
}

Finally, our listener looks like:

var itemListener: ValueEventListener = object : ValueEventListener {
        override fun onDataChange(dataSnapshot: DataSnapshot) {
            // Get Post object and use the values to update the UI
            addDataToList(dataSnapshot)
        }

        override fun onCancelled(databaseError: DatabaseError) {
            // Getting Item failed, log a message
            Log.w("MainActivity", "loadItem:onCancelled", databaseError.toException())
        }
    }

    private fun addDataToList(dataSnapshot: DataSnapshot) {

        val items = dataSnapshot.children.iterator()
        //Check if current database contains any collection
        if (items.hasNext()) {
            val toDoListindex = items.next()
            val itemsIterator = toDoListindex.children.iterator()
            
            //check if the collection has any to do items or not
            while (itemsIterator.hasNext()) {


                //get current item
                val currentItem = itemsIterator.next()
                val todoItem = ToDoItem.create()


                //get current data in a map
                val map = currentItem.getValue() as HashMap<String, Any>


                //key will return Firebase ID
                todoItem.objectId = currentItem.key
                todoItem.done = map.get("done") as Boolean?
                todoItem.itemText = map.get("itemText") as String?

                toDoItemList!!.add(todoItem);
            }
        }


        //alert adapter that has changed
        adapter.notifyDataSetChanged()
    }

What each line does is explained in above code itself. Now, we’re ready to show the data in our ListView. Once we run our app now, we can see the data as:

That’s great !! Our data is clearly visible now. As of now, we only can create and read our data.

We are left with two operations. Updating the item as done or undone by checking that CheckBox. Second by clicking on that cross image to delete this data.

Update and Delete ToDo items

Now, to perform these operations. We must know when each of our row is clicked. Also, we need to get the ObjectID of the row that was clicked. This can be obtained from the Adapter.

We will implement Observer pattern in this scenario which is an ideal implementation about how to interact between an adapter and an activity.

Let’s start by creating an Kotlin Interface file as:

package com.appsdeveloperblog.firebasetodo

interface ItemRowListener {

   fun modifyItemState(itemObjectId: String, isDone: Boolean)
   fun onItemDelete(itemObjectId: String)
}

Clearly, we want to provide two operations.

Next, we will make our activity implement this interface by doing:

class MainActivity : AppCompatActivity(), ItemRowListener {

As we’ve implemented an interface, we must implement its methods as well:

override fun modifyItemState(itemObjectId: String, isDone: Boolean) {
   val itemReference = mDatabase.child(Constants.FIREBASE_ITEM).child(itemObjectId)
   itemReference.child("done").setValue(isDone);
}


//delete an item
override fun onItemDelete(itemObjectId: String) {


   //get child reference in database via the ObjectID
   val itemReference = mDatabase.child(Constants.FIREBASE_ITEM).child(itemObjectId)
   //deletion can be done via removeValue() method
   itemReference.removeValue()
}

We need to call these methods via our adapter. In our getView(…) method, we can do as:

vh.isDone.setOnClickListener {
    rowListener.modifyItemState(objectId, !done) }


vh.ibDeleteObject.setOnClickListener {
    rowListener.onItemDelete(objectId) }

This can only be done if we define rowListener in our Adapter class:

private var rowListener: ItemRowListener = context as ItemRowListener

Now, once we click on checkbox as done, our database will change as:

Value has been changed.

Persisting Firebase data offline

Firebase is excellent in caching data. Once you run the app and get your data, Firebase will cache to so that it can be used offline as well. Once your app is back online, it will update the data.

The caching behaviour is off by default. Let’s modify this behavior.

We will create an Application class now:

package com.appsdeveloperblog.firebasetodo

import android.app.Application
import com.google.firebase.database.FirebaseDatabase

class ThisApplication: Application() {

   override fun onCreate() {
       super.onCreate()

       //Enable Firebase persistence for offline access
       FirebaseDatabase.getInstance().setPersistenceEnabled(true)
   }
}

That’s it! Just a single line of code and caching is enabled. Of course, you need to add this Application class in your Manifest file:

<application
   android:allowBackup="true"
   android:icon="@mipmap/ic_launcher"
   android:label="@string/app_name"
   android:roundIcon="@mipmap/ic_launcher_round"
   android:supportsRtl="true"
   android:name=".ThisApplication"
   android:theme="@style/AppTheme">

  ....
</application>

Our full fledged To Do Item application is ready.

Conclusion

In this lesson, we looked at CRUD(Create, Read, Update, Delete) operations of Firebase and how fast it is to sync with web server. We completed CRUD operations in our elegant app and enjoyed it as well.

Firebase also has strong caching techniques through which it promises strong user experience by presenting them data even when there is no network. We can do a lot more with Firebase like saving images etc. Let’s save it for another time.

To learn more about Mobile Apps Development with Kotlin check out the below books and video courses, they will certainly help you speed up your learning progress.

Happy learning!

Kotlin. Video Courses.

The Complete Android Kotlin Developer Course

Learn how to make online games, and apps for Android O, like Pokémon , twitter,Tic Tac Toe, and notepad using Kotlin. The Complete Android Kotlin Developer Course icon

Kotlin for Java Developers

Use your Java skills to learn Kotlin fast. Enhance career prospects and master Kotlin, including Java interoperability. Kotlin for Java Developers icon

Kotlin for Beginners: Learn Programming With Kotlin

Learn Kotlin from scratch! Grasp object-orientation and idiomatic Kotlin to realize coding projects and Android apps! Kotlin for Java Developers icon

Leave a Reply

Your email address will not be published. Required fields are marked *