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