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.
Related
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.
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.
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.
I am been developing a kotlin app with firebase as database and I am trying to have a loading screen to pass to another activity, and I am trying to keep everything organized so I am trying to have a code that when the value above is loaded pass to another activity
var myRef = database?.
getReference(tags?.USERS_DATABASE_VALUE.toString()).
child(mAuth?.currentUser!!.uid)
myRef?.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
snapshot?.children?.forEach {
val map = it.value as HashMap<String, Any>?
if (map!!.containsKey(tags?.COURSE_TAG_NAME)) {
if (map[tags?.COURSE_TAG_NAME].toString().equals(sharedPreferences?.getString(tags?.CURRENT_COURSE_ACTIVITY, null).toString())) {
var courseStats = map[tags?.COURSE_ADVANCEMENT]
// if (loaded)
// {
// GoToAnotherActivity
// }
}
}
}
}
override fun onCancelled(p0: DatabaseError) {
Toast.makeText(applicationContext, p0.message, Toast.LENGTH_SHORT).show()
}
})
When you call something from firebase database it has its own 'loading time' as Frank van Puffelen said in a comment below my question
Well good morning,
Today my project was to work a bit with Kotlin firebase. Just found a problem I can't seem to find any good documentation on how to store all child's in an array which I then can do a "for" loop on.
Try this one.
var firebaseDatabase: FirebaseDatabase
firebaseDatabase = FirebaseDatabase.getInstance();
progressBar.visibility = ProgressBar.VISIBLE
firebaseDatabase.getReference("players").addValueEventListener(object: ValueEventListener {
override fun onDataChange(p0: DataSnapshot?) {
Log.d("DAta",p0.toString())
progressBar.visibility = ProgressBar.GONE
if(p0!!.exists()){
for (h in p0.children){
val player = h.getValue(Player::class.java)
photosList.add(player)
adapter.notifyDataSetChanged()
Log.d("Data","player is "+player.toString())
}
}
}
override fun onCancelled(p0: DatabaseError?) {
Toast.makeText(activity,"Fail to Load Data", Toast.LENGTH_SHORT).show()
}
});
You can try this library https://github.com/Link184/Respiration
#FirebaseRepository(dataSnapshotType = MyModel.class,
persistence = true,
isAccessPrivate = true,
children = {PlanEventRepository.USERS_CHILD, FirebaseRepository.USER_ID, PlanEventRepository.PLAN_EVENTS_CHILD})
public class MyRepo extends FirebaseListRepository<MyModel> {
static final String USERS_CHILD = "users";
static final String PLAN_EVENTS_CHILD = "events";
protected MyRepo(Configuration<MyModel> configuration) {
super(configuration);
}
}
you can access your list in that way:
//MyRepoBuilder is a generated class by annotation processor and can be accedes only after a successful gradle build.
MyRepoBuilder.getInstance().subscribeToList(new SubscriberRespiration<List<MyModel>>() {
#Override
public void onSuccess(List<MyModel> dataSnapShot) {
//here is your list =)
}
});
Kotlin annotation processing is not supported yet so you must use java on your custom repositories.
All you want is to loop through all children of a DataSnapShot?
If so, a MutableList should work.
You can try something like this.
databaseReference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
elements = dataSnapshot.children.toMutableList()
for (element in elements) {
//your logic
}
}
override fun onCancelled(databaseError: DatabaseError) {}
})
There are two storage option for Firebase and it wasn't clear in the question which one is used. In our project we are using FirebaseFirestore and saving and retrieving array/list data is pretty straight forward. Save the object as list and just use toObject. For example below querying all the contents of the DB
val allEntities = mutableListOf<YourEntityClass>()
val fireStore = FirebaseFirestore.getInstance()
val ref = fireStore.collection("yourcollection")
ref.get().addOnSuccessListener { documents ->
documents.forEach {
it.toObject(YourEntityClass::class.java).let { entity ->
allEntities.addOrReplace(entity)
}
}
}