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.
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.
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)