Search code examples
androidfirebase-realtime-databasekotlinandroid-listviewnotifydatasetchanged

ListView updates only in onCreate() method when using Firebase


I'm writing ToDo list, and I want ListView to display changes, when user adds new taks to the list, or removes them. I'm not sure why, but adapter.notifyDataSetChanged() works only when app uses onCreate() method (turning on, changing to horizontal).

I tried to put adapter.notifyDataSetChanged() everywhere and it does not update content. Only after adding task to database (it shows on Firebase console) and changing to horizontal/restarting you can see new entery in ListView. I also created button, that uses only adapter.notifyDataSetChanged() in onClick() method.

MainActivity.kt :

package com.example.toodoo

import android.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.ListView
import android.widget.Toast
import com.google.firebase.database.*

class MainActivity : AppCompatActivity(), ItemRowListener {

//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
var toDoItemList: MutableList<ToDoItem>? = null
lateinit var adapter: ToDoItemAdapter
private var listViewItems: ListView? = null

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()
}

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

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 ->
        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()
    }
    alert.show()

}

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()
}

fun onClick(view: View){
    adapter.notifyDataSetChanged()
}
}

I think it might be also a problem connected with syncing Firebase data with ListView, but I'm using it first time and I cannot resolve that problem. It's my first time with that database, so mabe I'm missing something out.


Solution

  • You're adding a listener for the data with:

    mDatabase.orderByKey().addListenerForSingleValueEvent(itemListener)
    

    Since you're using addListenerForSingleValueEvent, this only listens for the current data, and then removes the listener. So after loading the current data, you're no longer listening for changes.

    To continue to listen for the initial data and changes afterwards, use:

    mDatabase.orderByKey().addValueEventListener(itemListener)
    

    Note that you might want to empty toDoItemList in addDataToList, as you'll otherwise be adding all existing items again and again whenever there's a change.

    Or alternatively use addChildEventListener, which gives you more granular information on what has changed about the child nodes, so that you perform fine-grained updates to the adapter.