Categories
Android Firebase Kotlin

Firebase Data Logging Android App with Custom ListView Adapter in Kotlin

This blog post explains the creation of an Android application for data logging on Firebase Realtime Database. This could be used for logging and managing various types of embedded systems sensors readings on Firebase Real time Database with timestamps. We developed this application in Kotlin. The application enables users to sign in and view real-time log entries in a ListView. This ListView uses a custom ListView Adapter Class to include a delete button for each entry and also to display custom layout for each entry.

Log entries in Firebase realtime database
Log entries in Firebase

Learning Objectives

Let’s summarize what are we going to cover in this post.

  • Create a custom data class, LogData, to represent log entries. This class will have sensor values and timestamps
  • Read a Custom Data Class object from the Firebase Realtime database as data snapshot
  • Delete a specific Log entry from Firebase Real time database based on the specific timestamp
  • Handle error and success messages when reading and writing data to Firebase and show as toast using Toast.makeText
  • Reading all log values and display them in ListView
  • Make a custom mutable list for custom data class
  • Create a custom ListView Adapter for custom layout with Delete button.

Let’s start developing. I assume you already created a new Firebase Integrated project in Kotlin in your Android Studio. If you do not know the basics, please check my previous articles on Firebase and Android. Here are two main articles you should visit for basics.

Reading data from firebase realtime database in Android Kotlin
Creating Firebase based Project in Android Studio Kotlin language

MainActivity Layout File

Here is the complete xml layout file which includes one Button for signout functionality. One TextView for displaying current Sensor Reading values. One TextView for displaying the timestamp and one ListView to display all logged values from firebase realtime database. We used the LinearLayout for simplicity.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity2"
    android:padding="16dp"
    >

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/signout"
        />

    <TextView
        android:id="@+id/tempVal"
        android:layout_width="320dp"
        android:layout_height="wrap_content"
        android:textSize="32sp"
        android:textColor="@color/black"
        android:text="Temp:"
        />
    <TextView
        android:id="@+id/tvNow"
        android:layout_width="320dp"
        android:layout_height="wrap_content"
        android:text=""
        />
    <ListView
        android:id="@+id/logListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
Code language: HTML, XML (xml)

ListView Custom Layout file with Delete Button

We know that making a Listview in android is pretty simple and straightforward. We can use pre built layout adapters which are simple and could represent a simple string values. One thing to remember here, If you are using the large amount of data make sure to use RecyclerView instead of ListView. We do not need to create a custom layout file just for single string value entry in the ListView. We can use this piece of code which will take care of it.

val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, logList)
logListView.adapter = adapter
Code language: Kotlin (kotlin)

This R.layout.simple_list_item_1 layout file is enough for displaying the simple strings in the ListView. But we want to add a delete button and a timestamp display as well. We can create our custom layout for single entry in the ListView. Here is how we create a new layout file in the res/layout folder. We give it name of log_list_item.xml. Here is the code for that layout.

<?xml version="1.0" encoding="utf-8"?>
<!-- log_list_item.xml -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/logTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:padding="8dp"
        android:textSize="16sp"/>

    <Button
        android:id="@+id/deleteButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Delete"/>
</LinearLayout>

Code language: HTML, XML (xml)

Custom Data Class LogData

Let’s now create a custom data class for LogData. This class will be used to decode the incoming data snapshot from firebase realtime database. Also, for the custom ListView adapter class. Here is the simple data class definition.

package com.any.firebase.example

// Data class to represent log entries
data class LogData(val value: String?, val timestamp: String) {
    // Add a no-argument constructor
    constructor() : this("", "")
}

Code language: Kotlin (kotlin)

Custom Data Adapter With Delete Log Entry from Firebase Database

Now we are going to create a custom data adapter to display the log data in the listview with the custom delete function. This will also remove the entries back on the firebase on Delete button click. When user click on delete button it will remove the entries from the firebase real time database. Also, once new value added, it will append that value on the ListView. It will automatically bind the Log value and the timestamp on the custom listview layout file. Here is the complete custom data adapter for the LogData Listview.

 private inner class LogDataAdapter(
        context: AppCompatActivity,
        private val resource: Int,
        objects: List<LogData>
    ) : ArrayAdapter<LogData>(context, resource, objects) {

        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
            val itemView = convertView ?: layoutInflater.inflate(resource, parent, false)
            val logData = getItem(position)

            val logTextView: TextView = itemView.findViewById(R.id.logTextView)
            val deleteButton: Button = itemView.findViewById(R.id.deleteButton)

            // Display log data
            logTextView.text = "Time: ${logData?.timestamp}:, Value: ${logData?.value}"

            // Set click listener for the delete button
            deleteButton.setOnClickListener {
                // Delete the corresponding log entry from Firebase
                logData?.let { deleteLogEntry(it) }
            }

            return itemView
        }
    }

    private fun deleteLogEntry(logData: LogData) {
        val logdbReference = Firebase.database.getReference("logs")
        // Find the corresponding log entry in the Realtime Database and remove it
        logdbReference.orderByChild("timestamp").equalTo(logData.timestamp)
            .addListenerForSingleValueEvent(object : ValueEventListener {
                override fun onDataChange(snapshot: DataSnapshot) {
                    for (childSnapshot in snapshot.children) {
                        childSnapshot.ref.removeValue()
                            .addOnSuccessListener {
                                Log.d(TAG, "Log entry removed successfully")
                                Toast.makeText(
                                    applicationContext,
                                    "Log entry removed successfully",
                                    Toast.LENGTH_SHORT
                                ).show()
                            }
                            .addOnFailureListener { e ->
                                Log.w(TAG, "Error removing log entry", e)
                                Toast.makeText(
                                    applicationContext,
                                    "Failed to remove log entry",
                                    Toast.LENGTH_SHORT
                                ).show()
                            }
                    }
                }

                override fun onCancelled(error: DatabaseError) {
                    Log.w(TAG, "Failed to read logs.", error.toException())
                }
            })
    }
Code language: Kotlin (kotlin)

Properly Initialize and Configure ListView

Now we are going to bind everything together from the layout file Listview with a ListView Item through the Custom Data class named LogData and the custom data adapter class we just created in the previous section. Here is the code which is doing this for us.

private lateinit var logListView: ListView
private lateinit var logList: MutableList<LogData>


 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        logListView = findViewById(R.id.logListView)
        logList = mutableListOf()
        val adapter = LogDataAdapter(this, R.layout.log_list_item, logList)
        logListView.adapter = adapter
}
Code language: JavaScript (javascript)

Create LogData Entry to Firebase Realtime Database

Now let’s create a function which takes a value and create a Log data entry on the firebase realtime database. Here is the code for that function.

fun getCurrentDateTime(): String {
        val calendar = Calendar.getInstance()
        val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
        return dateFormat.format(calendar.time)
    }

  private fun saveValueInRealtimeDatabase(value: String?, timestamp: String) {
        // Create a new log entry with a generated ID
        val logData = hashMapOf(
            "value" to value,
            "timestamp" to timestamp
        )

        val databaseReference = Firebase.database.reference
        // Add a new log entry with a generated ID
        databaseReference.child("logs").push().setValue(logData)
            .addOnSuccessListener {
                Log.d(TAG, "Log data added to Realtime Database")
                Toast.makeText(baseContext, "Log data added to Realtime Database", Toast.LENGTH_SHORT).show()
            }
            .addOnFailureListener { e ->
                Log.w(TAG, "Error adding log data to Realtime Database", e)
                Toast.makeText(baseContext, "Failed to add log data to Realtime Database", Toast.LENGTH_SHORT).show()
            }
    }

Code language: Kotlin (kotlin)

We can use this function like this.

                val value = snapshot.getValue<String>()                
                val dateTimeNow = getCurrentDateTime()
                saveValueInRealtimeDatabase(value,dateTimeNow)
Code language: Kotlin (kotlin)

Handle Firebase ValueChange for Log Entries

Now we are going to capture the event which will be fired if any new entry is made on the Firebase Real time database under the logs section in the database. This will update the list view to display the new entry in the database. Here is the code for the value change event listener.

val logdbReference = Firebase.database.getReference("logs")
        // Add a listener to retrieve logs from Firebase Realtime Database
        logdbReference.addChildEventListener(object : ChildEventListener {
            override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
                // This method is called when a new child is added to the "logs" node
                val logData = snapshot.getValue(LogData::class.java)
                if (logData != null) {
                    // Format log data and add it to the list
                    val formattedLog = "Time: ${logData.timestamp}:,  Value: ${logData.value} C"
//                    logList.add(formattedLog)
                    logList.add(logData)

                    // Update the ListView
                    adapter.notifyDataSetChanged()
                }
            }

            override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
                // Handle child data changes if needed
            }

            override fun onChildRemoved(snapshot: DataSnapshot) {
                val logData = snapshot.getValue(LogData::class.java)
                logList.remove(logData)
                adapter.notifyDataSetChanged()
            }

            override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
                // Handle child movement if needed
            }

            override fun onCancelled(error: DatabaseError) {
                Log.w(TAG, "Failed to read logs.", error.toException())
            }
        })

    }
Code language: Kotlin (kotlin)

By Abdul Rehman

My name is Abdul Rehman and I love to do Reasearch in Embedded Systems, Artificial Intelligence, Computer Vision and Engineering related fields. With 10+ years of experience in Research and Development field in Embedded systems I touched lot of technologies including Web development, and Mobile Application development. Now with the help of Social Presence, I like to share my knowledge and to document everything I learned and still learning.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.