How to update particular value of child in Firebase Database - android

I want to update the last message, try multiple methods but little bit confused what is the exact code to update the last message. Thanks in advance. Below is the code that I have tried:
databaseReference = FirebaseDatabase.getInstance().reference.child("user").child(apiKey)
databaseReference.orderByChild("api_key").equalTo(loginModel.api_key).addValueEventListener(object : ValueEventListener{
override fun onCancelled(error: DatabaseError) {
}
override fun onDataChange(snapshot: DataSnapshot) {
if(snapshot.exists()){
val map = mutableMapOf<String, String>()
map["last_message"] = message
map["last_message_key"] = apiKey
//databaseReference.child("api_key").child(loginModel.api_key).updateChildren(map.toMap())
//databaseReference.updateChildren(map.toMap())
databaseReference.child(snapshot.key!!).setValue(chatUser)
Utils.showToast(requireContext(), "Exists")
}else{
Utils.showToast(requireContext(), "Not Exists")
userReference.setValue(chatUser)
}
}
})

It would be useful if you posted the code and also explained the outcome of your code for us to understand better.
Example from the docs: mDatabase.child("users").child(userId).child("username").setValue(name); You can call .child all the way to your preferred child and do a setValue().
From your code, the issue might be with the snapshot.key!! value which seems to be pointing back to your apiKey value.
What you need is mDatabase.child("user").child("2c3e...").child("-MXH..").updateChildren(). setValue() will overwrite everything in that node. You should debug to see what values you are seeing in each step.

To be able to update properties in Firebase Realtime Database when using a Query, then you should call getRef() directly on the DataSnapshot object, as explained in the following lines of code:
val rootRef = FirebaseDatabase.getInstance().reference
val apiKeyRef = rootRef.child("user").child("apiKey")
val query = apiKeyRef.orderByChild("api_key").equalTo(loginModel.api_key)
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
if(dataSnapshot.exists()) {
for (ds in dataSnapshot.children) {
val map = mutableMapOf<String, String>()
map["last_message"] = message
map["last_message_key"] = apiKey
ds.getRef().updateChildren(map)
}
}
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("TAG", databaseError.getMessage()) //Don't ignore potential errors!
}
}
query.addListenerForSingleValueEvent(valueEventListener)
I also encourage you to attach a complete listener to all update operations, to always know if something goes wrong.

Instead of use set() method i think you should use update() method. In that method you pass the object and firebase detect the properties that has been updated. I'm using it for my project and works fine.

Related

problem with kotlin update real database permanent loop

First of all. Thanks to everybody this place is awesome and full of people willing to help ;)
My question: I've created a function using Realtime Database to update the same time three values from three different children in the same table. And it works perfectly well if I update just one of them.
To launch the function the user can update from none to all three values together but the problem is that when the user modify more than one of the value firebase keep in a death loop updating the values continuously
My DB
My function is here:
private fun guardarTokens () {
referenciaBD2 = FirebaseDatabase.getInstance().getReference("TipoUsuario")
val tipoUsuarioDatos = HashMap<String, Any>()
referenciaBD2.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
for (snapshot in snapshot.children) {
val tipoUsuarioInfo = snapshot.getValue(TipoUsuario::class.java)
if (tipoUsuarioInfo!!.descripcionUsuario == "usuario")
tipoUsuarioDatos["tuid"] = binding.etTokenAlumno.text.toString()
if (tipoUsuarioInfo!!.descripcionUsuario == "profesor")
tipoUsuarioDatos["tuid"] = binding.etTokenProfesor.text.toString()
if (tipoUsuarioInfo!!.descripcionUsuario == "administrador")
tipoUsuarioDatos["tuid"] = binding.etTokenAdmin.text.toString()
snapshot.ref.updateChildren(tipoUsuarioDatos)
}
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
That's not how you should update a specific element in the Realtime Database. What you're actually doing, you're downloading the entire TipoUsuario node on the client in order to perform a verification. That is considered a waste of resources and bandwidth. What you should do instead, is to perform a query and get only the data you are interested in:
referenciaBD2 = FirebaseDatabase.getInstance().getReference("TipoUsuario")
val queryByUsuario = referenciaBD2.orderByChild("descripcionUsuario").equalTo("usuario")
val tipoUsuarioDatos = HashMap<String, Any>()
queryByUsuario.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
for (snapshot in snapshot.children) {
val tipoUsuarioInfo = snapshot.getValue(TipoUsuario::class.java)
tipoUsuarioDatos["tuid"] = binding.etTokenAlumno.text.toString()
snapshot.ref.updateChildren(tipoUsuarioDatos)
}
}
}
override fun onCancelled(error: DatabaseError) {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
})
In this way, the query will only return the children where the descripcionUsuario field holds the value of usuario.

How to wait for a function to complete in android (kotlin)?

I am a beginner in Android development. I am trying to implement RecyclerView which shows a list of groups after downloading it from the Realtime Database.
The function loadGroups() is called from the main activity to return a list which is then fed to the RecyclerView adapter.
The data is being downloaded correctly but seems like myList is returned first and elements from firebase are added a few millis later. I want the program to wait for the elements to be added to myList and then return it
class DataSource {
private lateinit var myDatabase: DatabaseReference
var myList : MutableList<Group> = mutableListOf<Group>();
fun loadGroups(): MutableList<Group> {
// Here I want to let the loadGroupsFromFirebase() complete adding groups to mylist
// after that is completed, mylist should be returned
loadGroupsFromFirebase()
Log.d("mylist", "returning my list")
return myList
}
private fun loadGroupsFromFirebase(){
myDatabase = FirebaseDatabase.getInstance().getReference("myGroupsList")
val postListener = object : ValueEventListener {
override fun onDataChange(myDataSnapshot: DataSnapshot) {
if(myDataSnapshot.exists()){
Log.d("mylist", "does exist ${myDataSnapshot.getValue().toString()}")
myList.clear()
for(dataSnapshot in myDataSnapshot.children){
val myGroupDetails = dataSnapshot.getValue<Group>()!!;
myList.add(myGroupDetails)
myList.add(Group(myIconId=2131165282, myTitle="G1", myLink = "https://s*****************************************9", numberOfPeople=100))
Log.d("mylist", "does exist CODE 00 ${myList.toString()}")
}
}
else {
Log.d("mylist", "does not exist")
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w("mylist", "loadPost:onCancelled", databaseError.toException())
}
}
myDatabase.addValueEventListener(postListener)
}
}
Any help would be appreciated :)
Below is the screenshot of logcat.
ValueEventListener is an interface, so that everything that runs on onDataChange is an asyncronous operation (because depends on network response), in other hand loadGroups() is a synchronous operation, so loadGroupsFromFirebase() is call at first and inmediatly return myList is called, but loadGroupsFromFirebase() is not finish yet.
So you need to use the groups after the for loop
for(dataSnapshot in myDataSnapshot.children){
val myGroupDetails = dataSnapshot.getValue<Group>()!!;
myList.add(myGroupDetails)
myList.add(Group(myIconId=2131165282, myTitle="G1", myLink = "https://s*****************************************9", numberOfPeople=100))
Log.d("mylist", "does exist CODE 00 ${myList.toString()}")
}
//-- HERE, TAKE myList and save it somehow.
Or well, implements MVP or MVVM patters, so you can handle the asyncronous response properly.
Modify your function loadGroupsFromFirebase() have a unit return
private fun loadGroupsFromFirebase(result: (List<Group>) -> Unit){
myDatabase = FirebaseDatabase.getInstance().getReference("myGroupsList")
val postListener = object : ValueEventListener {
override fun onDataChange(myDataSnapshot: DataSnapshot) {
if(myDataSnapshot.exists()){
Log.d("mylist", "does exist ${myDataSnapshot.getValue().toString()}")
myList.clear()
for(dataSnapshot in myDataSnapshot.children){
val myGroupDetails = dataSnapshot.getValue<Group>()!!;
myList.add(myGroupDetails)
myList.add(Group(myIconId=2131165282, myTitle="G1", myLink = "https://s*****************************************9", numberOfPeople=100))
Log.d("mylist", "does exist CODE 00 ${myList.toString()}")
}
}
else {
Log.d("mylist", "does not exist")
}
result(myList)
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w("mylist", "loadPost:onCancelled", databaseError.toException())
}
}
myDatabase.addValueEventListener(postListener)
}
There is no way you can synchronically return myList as a result of a method because Firebase API is asynchronous. The solution is always the same, any code that needs data from the Realtime Database needs to be inside the onDataChange() method, or be called from there.
Since you're using Kotlin, please note that we have a simpler way to solve the asynchronous calls. So I'll show you how to use Kotlin Coroutines.
In order to make it work, we need to add the following dependency in our build.gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4"
This library that we use is called Module kotlinx-coroutines-play-services. As I mentioned before, there is no way we can return a list of objects as a result of a method because get() returns immediately, while the callback from the Task it returns will be called sometime later. That's the reason why we should wait until the data is available.
When calling "get()" on the Task object that is returned, we can attach a listener so we can get the result of our query. What we need to do now is to convert this into something that is working with Kotlin Coroutines. For that, we need to create a suspend function that looks like this:
private suspend fun getGroupList(): List<DocumentSnapshot> {
val snapshot = myDatabase.get().await()
return snapshot.documents
}
As you can see, we have now an extension function called await() that will interrupt the Coroutine until the data from the Realtime Database is available and then return it. Now we can simply call it from another suspend method like in the following lines of code:
private suspend fun getGroupListFromFirestore() {
try {
val groupList = getGroupList()
} catch (e: Exception) {
Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
}
}
And that's pretty much of it. If you are interested, I have also written an article called:
How to read data from Cloud Firestore using get()?
That will help you better understand the concept.

Firebase Realtime Database wait until the data is retrieved

I have a map containing the keys of my Firebase Realtime Database and want to retrieve the corresponding key data and put it in the result data list. How can I execute the loop sequentially? Basically, block the Firebase listener until it gets the result and only then iterate to the next key in the loop.
fun functionA() {
val resultFileDataList = List<DataSnapshot>()
for ((key, value) in filesMap) {
val dbRef = database.child("files").child(key)
dbRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {}
override fun onDataChange(dataSnapshot: DataSnapshot) {
resultFileDataList.add(dataSnapshot)
}
})
}
callFunctionB() // call this function only after all the data in the loop above is retrieved
}
I tried runBlocking {} but no luck.
You can achieve it using this way by utilizing the Task. Tasks.whenall() will wait until all task are done.
fun functionA() {
val taskList = mutableListOf<Task<DataSnapshot>>()
val resultFileDataList = List<DataSnapshot>()
for ((key, value) in filesMap) {
val databaseReferenceTask: Task<DataSnapshot> = database.child("files").child(key).get()
taskList.add(databaseReferenceTask)
val resultTask = Tasks.whenAll(taskList)
resultTask.addOnCompleteListener {
for (task in taskList) {
val snapshotKey: String? = task.result.key
val snapShotValue = task.result
}
callFunctionB()
}
}
}
Since you are using Kotlin, then the simplest solution would be to use Kotlin Coroutines. In this way, you can use suspend functions and call await for each read operation. To achieve that, please check the following article:
How to read data from Firebase Realtime Database using get()?
If you need however to pipeline the requests over its existing connection, then you should consider using kotlinx-coroutines-play-services, case in which you can use awaitAll() function.
This is one way to do it:
suspend fun functionA() = suspendCoroutine<List<DataSnapshot>>{ continuation ->
val resultFileDataList = mutableListOf<DataSnapshot>()
for ((key, value) in filesMap) {
val dbRef = database.child("files").child(key)
dbRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {}
override fun onDataChange(dataSnapshot: DataSnapshot) {
resultFileDataList.add(dataSnapshot)
if(resultFileDataList.size == fileMaps.size){
continuation.resume(resultFileDataList)
}
}
})
}
}
And then you can call the functions wherever you want like so:
CoroutineScope(Dispatchers.IO).launch {
val dataSnapshotList = functionA()
functionB(dataSnapshotList)
}
Bear in mind that it is better to use the following to bind the coroutine to the lifecycle of the activity:
lifecycleScope.launch(Dispatchers.IO) {
val dataSnapshotList = functionA()
functionB(dataSnapshotList)
}
Note:
This will basically wait for all the data to change so that the onDataChanged() is triggered and when the last file is added, continues with the coroutine and returns the value. Depending on your user's behaviour, this could take a long time to complete since even if one of the files is not changed, the coroutine will not resume.
Also, if onCancelled() is triggered for one file, this will never complete. So if you are absolutely sure that onDataChanged() will be triggered for all files, use this. Otherwise, implement some sort of timeout functionality to resume with the incomplete data.

How can i retrieve values of Firebase Nodes with random auto generated IDs in Android Using Kotlin

I don't know how to go about retrieving data from the Firebase Realtime Database where the values have randomly generated IDs as seen below.
To be able to get all the keys and values that exist under the 21-6-21 node, you need to loop through the DatasnaShot object, as in the following lines of code:
val rootRef = FirebaseDatabase.getInstance().reference
val dateRef = rootRef.child("SigninData").child("CSC101").child("21-6-21")
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (ds in dataSnapshot.children) {
val key = ds.getkey()
val value = ds.getValue(String::class.java)
Log.d("TAG", "$key/$value")
}
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("TAG", databaseError.getMessage()) //Don't ignore potential errors!
}
}
dateRef.addListenerForSingleValueEvent(valueEventListener)
The result in the logcat will be:
-Mf8...ESM/oma#gmail.com...
-Mf8...7nb/oma#gmail.com...
-Mf8...XJv/oma#gmail.com...
Edit:
private fun getDataFrom(date: String) {
val rootRef = FirebaseDatabase.getInstance().reference
val dateRef = rootRef.child("SigninData").child("CSC101").child(date)
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (ds in dataSnapshot.children) {
val key = ds.getkey()
val value = ds.getValue(String::class.java)
Log.d("TAG", "$key/$value")
}
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("TAG", databaseError.getMessage()) //Don't ignore potential errors!
}
}
dateRef.addListenerForSingleValueEvent(valueEventListener)
}
Now you can call the above method either with:
getDataFrom("21-6-21")
Or:
getDataFrom("22-6-21")
If you need to get them both, then you should use a get() call, and pass both Task objects to the whenAllSuccess(Task...<?> tasks).
I believe this will work out for you.
fun getDataFromFirebaseDatabase() {
// Your path
database.getReference("SigninData/CSC101/21-6-21")
.get()
.addOnCompleteListener {
//this checks if there were any exceptions or Errors generated and will Log the error in your Logcat.
if (it.exception != null) Log.e("FirebaseDatabase", "Error", it.exception)
// this checks if your result was successful or not and also,
//if it has children or not, if both conditions are satisfied,
//it will enter the for loop and print all the
// values along with their keys in logcat.
if (it.isSuccessful && it.result.hasChildren()) {
for (i in it.result.children) {
//i.key will give you required key
Log.d("FirebaseDatabase", "key is ${i.key}")
// i.value will give respective value
// import the values as given in database, for your case it would be String so.
val data = i.value as String
Log.d("FirebaseDatabase", "value is $data")
}
}
}
}
Note - This will read all the values inside 21-6-21 child
Your Logcat should look something like this.
//if there is an error
E/FirebaseDatabase: Error <Some Error Code Here>
//if Run is Successful
//since there are 3 entries in CSC101 node all three will be printed one by one
//like the example given below
D/FirebaseDatabase: key is Mf80wSd9neOAqlPhESM
D/FirebaseDatabase: value is oma#gmail.com You signed in for CSC101
The above code uses get() function which means your app will only read the database only once.
Instead of get() you can also use:
addChildEventListener()
addValueEventListener()
addListenerForSingleValueEvent()
All are for different purposes.
Here are the Firebase Docs regarding these: https://firebase.google.com/docs/database/android/read-and-write#read_data
the 3rd one is similar to the get function but 3rd one will read from the cache memory and should be used when you are not expecting your data to change frequently.
1st and 2nd will get the results from the database as soon as there are changes made in the database nodes.

Firebase Database EventListener outside of an activity

I am new to Android and Kotlin and I want to implement a FirebaseRetriever class in my project that returns a specific database snapshot.
Now I am having issues that the EventListener is never triggered and so, of course, I get a null object back.
Here is my data class:
public class FirebaseRetriever() {
private val TAG = "FirebaseRetriever"
private lateinit var fbData: FirebaseDatabase
private lateinit var fbAuth: FirebaseAuth
private lateinit var userRef: DatabaseReference
//Snapshots
private lateinit var userSnap: DataSnapshot
init {
fbData = FirebaseDatabase.getInstance()
userRef = fbData.reference.child("users")
fbAuth = FirebaseAuth.getInstance()
userRef.addValueEventListener(object : ValueEventListener {
override fun onCancelled(e: DatabaseError) {
Log.e(TAG, "Data could not be retrieved from Database." + e.message)
}
override fun onDataChange(snap: DataSnapshot) { // is never executed
userSnap = snap
}
})
}
public fun getUserSnap(): DataSnapshot {
return userSnap // returns null
}
}
This is how I call the getUser():
firebaseRetriever = FirebaseRetriever()
...
val uniSnap = firebaseRetriever.getUniSnap()
Is it possible that a Firebase EventListener can only work in a regular AndroidActivity or am I doing something else wrong?
Thanks in advance.
Most likely the data simply hasn't been loaded yet when you call getUserSnap. To verify this, add a few more log statements:
Log.i(TAG, "Starting to read from database.")
userRef.addValueEventListener(object : ValueEventListener {
override fun onCancelled(e: DatabaseError) {
Log.e(TAG, "Data could not be retrieved from Database." + e.message)
}
override fun onDataChange(snap: DataSnapshot) { // is never executed
Log.i(TAG, "User read from database.")
userSnap = snap
}
})
public fun getUserSnap(): DataSnapshot {
Log.i(TAG, "Getting user.")
return userSnap // returns null
}
When you run your app with this the output will likely be:
Starting to read from database.
Getting user.
User read from database.
That is probably not the order you expected. Firebase loads data from the database asynchronously, since it may take some time for the results to be available. Instead of making your app wait (which would trigger an Application Not Responding dialog), your code is allowed to continue. Bu the time you call getUserSnap, that user hasn't been loaded yet.
The simplest solution is to move all the code that needs access to the user into the onDataChange() method. Also see my answer to this question for a more flexible solution, that requires more code: getContactsFromFirebase() method return an empty list

Categories

Resources