How to retrieve data from a Firebase database with Kotlin? - android

This is the model I upload on Firebase:
public class OnlineMatch{
private User user1;
private User user2;
public OnlineMatch(User firstPlayer, User secondPlayer) {
this.user1 = firstPlayer;
this.user2 = secondPlayer;
}
}
Then I send data to Firebase in this way (kotlin):
fun createMatch(match: OnlineMatch) {
val matchList = database.child("multiplayer").push()
matchList.setValue(match)
}
Thus, my DB structure is the following:
If I expand a node I can see perfectly my objects: OnlineMatch(User1, User2)
Now I would like to query the db and obtain an ArrayList'<'OnlineMatch'>'.
I have already found the Firebase docs but I found nothing useful.
How can I do? Thanks in advance.

You did not find something useful because when you query against a Firebase database you get a Map and not ArrayList. Everything in Firebase is structured as pairs of key and value. Using an ArrayList is an anti-pattern when it comes to Firebase. One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write.
In Kotlin there is no need for getters and setters. Is true that behind the scenes those functions exists but there is no need to explicitly define them. To set those fields, you can use the following code:
val onlineMatch = OnlineMatch() //Creating an obect of OnlineMatch class
onlineMatch.user1 = userObject //Setting the userObject to the user1 field of OnlineMatch class
//onlineMatch.setUser(userObject)
As you probably see i have commented the last line because there is no need to use a setter in order to set a userObject.
And very important, don't forget to add the no argument constructor in your OnlineMatch class that is needed for Firebase.
public OnlineMatch() {}
Edit:
To actually get the data, just put a listener on the desired node and get the data out from the dataSnapshot object into a HashMap.
val map = HashMap<String, OnlineMatch>()
Then simply iterate over the HashMap like this:
for ((userId, userObject) in map) {
//do what you want with them
}
Or simply use the code below:
val rootRef = firebase.child("multiplayer")
rootRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(error: FirebaseError?) {
println(error!!.message)
}
override fun onDataChange(snapshot: DataSnapshot?) {
val children = snapshot!!.children
children.forEach {
println(it.toString())
}
}
})
Hope it helps.

Related

Expected methods do not exists MutableStateFlow<List<T>>() for manipulation

I have this MutableStateFlow<>() declaration:
private val _books = MutableStateFlow<List<Book>>(emptyList())
I am trying to append/add results from the database:
fun fetchAllBooks(user_id: Long) = viewModelScope.launch(Dispatchers.IO) {
dbRepository.getAllUsersBooks(user_id).collect{ books ->
_books.add() // Does not exist, nor does the 'postValue' method exists
}
}
But, this does not work as I though, non of the expected methods exists.
If you need to update the state of a MutableStateFlow, you can set the value property:
fun fetchAllBooks(user_id: Long) = viewModelScope.launch(Dispatchers.IO) {
dbRepository.getAllUsersBooks(user_id).collect{ books ->
_books.value = books
}
}
It will trigger events on collectors if the new value is different from the previous one.
But if getAllUsersBooks already returns a Flow<List<Book>>, you could also simply use it directly instead of updating a state flow.
If you really want a StateFlow, you can also use stateIn:
fun fetchAllBooks(user_id: Long) = dbRepository.getAllUsersBooks(user_id)
.flowOn(Dispatchers.IO) // likely unnecessary if your DB has its own dispatcher anyway
.stateIn(viewModelScope)
You are actually declaring the immutable list and trying to add and remove data instade of that use mutable list to add or remove data from list like here :-
private var _bookState = MutableStateFlow<MutableList<Book>>(mutableListOf())
private var books=_bookState.asStateFlow()
var bookList= books.value
and to send the data to the state use this:-
viewModelScope.launch {
_bookState.value.add(BookItem)
}
viewModelScope.launch {
_bookState.value.remove(BookItem)
}
I hope this will work out for you if you have any query pls tell me in comment.

When I delete a data in firebase, it is deleted from the database, but it is not deleted from the list without going to another activity

I'm listing Firebase user's favorite words. When the user presses the favButton, it will remove the favorite word. The favorite word is deleted in the database. But the favorite list is not updated immediately and the deleted word is still in the list. When I come back from another activity, it is deleted. How can I solve this? The place where I deleted the favorite word is in the adapter.
class DeleteFavorite: AppCompatActivity() {
private lateinit var database: DatabaseReference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
database = Firebase.database.reference
}
fun deleteFav(keyKelime: String?) {
val database = FirebaseDatabase.getInstance()
val userId = FirebaseAuth.getInstance().currentUser?.uid
val refFavori = database.getReference("/users/${userId}/favorites/${keyKelime}")
refFavori.removeValue().addOnSuccessListener {
Log.e("unfav","Fav geri alındı")
}
}
My adapter
holder.favButton.setOnClickListener {
if (!isClicked) {
holder.favButton.setBackgroundResource(R.drawable.ic_favorite_border)
val deleteFavorite = DeleteFavorite()
deleteFavorite.deleteFav(kelime.keyKelime)
} else {
isClicked = true
val detayActivity = DetayActivity()
detayActivity.writeNewFav(kelime?.keyKelime.toString())
holder.favButton.setBackgroundResource(R.drawable.ic_favorite_dark)
}
}
And my FavoriteWords
fun tumFavoriler(kelimelerListe: ArrayList<Kelimeler>, rvFav:RecyclerView){
adapter = KelimelerAdapter(layoutInflater.context,newKelimelerList)//bura kelime istiyoo
rvFav.adapter = adapter
refFavoriler.get().addOnSuccessListener {
for( c in it.children){
val favKelimeId = c.child("kelime_id").value
kelimelerListe.forEach {
if (it.kelime_id == favKelimeId){
newKelimelerList.add(it)
Log.e("it","$newKelimelerList")
}
}
}
adapter.notifyDataSetChanged()
}.addOnFailureListener{
}
That's because you use get() to read the data from the database, which (as this documentation says) reads the value only once.
If you want your app to respond to changes in the database, use a realtime listener as shown in the documentation section on reading data with a realtime listener.
You could listen to changes within the database as Frank mentioned, but you might not be interested in real-time updates as managing changing data can be difficult.
As an alternative, you can assume the data is successful on completion and update it locally, such as removing the item by index or key properties updating it locally.
This is a practice known as "Positive Assumptions".
This process does use the same logic you would with a snapshot listener, but it won't fire off changes until the user does a specific task.

How to query multiple nodes/references in Firebase

I have 2 nodes, user_account_settings and users. I want to write a query to get both nodes child where both nodes username is equal. I write a query but it is returning null. How to write a multi reference query.
Data Structure:
ViewModel Class:
private val databaseRef = FirebaseDatabase.getInstance().reference
.orderByChild("username")
.equalTo("faisal.askani")
private val liveData = FirebaseQueryLiveData(databaseRef)
ProfileFramgnet:
viewModel.getDataSnapshotLiveData().observe(viewLifecycleOwner, Observer { dataSnapshot ->
if(dataSnapshot != null)
{
for (ds: DataSnapshot in dataSnapshot.children)
{
//user node
if (ds.key.equals("users"))
{
val user = ds.getValue(User::class.java)
viewModel.setUserInfo(user!!)
}
if(ds.key.equals("user_account_settings"))
{
val userAccountInfo = ds.getValue(UserAccountSettings::class.java)
viewModel.setUserAccountInfo(userAccountInfo!!)
}
}
}
})
How to write a multi reference query?
There is no way you can perform a "multi reference query". You can get results only from a location that the query is run against, including all the children. In your particular case, you need to use two separate queries, once to get the user detalis and second to get the account details.
It's true that you can add a listener on your root reference and get all the children in the entire database but this is not an option since you'll need to download the content of your entire database, which is a waste of bandwidth and resources.

Firebase: any way to define a "any" node?

Ok, so this is what my Fireabse database looks like:
Now I need to update the "about" node for every user. Without the user node, this is what I would do:
val mAllUserDatabase : DatabaseReference = FirebaseDatabase.getInstance().reference.child("Users"))
val childUpdates = HashMap<String, Any>()
childUpdates["/about"] = "new value"
mAllUserDatabase.updateChildren(childUpdates)
The problem is, I need to define the node of the user (don't I?). But since the user node all have a different name, I would need something like "any- node" or "all nodes". Something like
childUpdates["/any/about"] = "new value"
or getChildren()of the snapshots.
What would be the best way to solve this? Do I have to use addListenerForSingleValueEvent to read the names of all nodes and then use the names on mAllUserDatabase with .updateChildren(childUpdates)? That cant be the best way to do this.
To update your about property within all user objects, please use the following lines of code:
val rootRef = FirebaseDatabase.getInstance().reference
val usersRef = rootRef.child("Users")
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (ds in dataSnapshot.children) {
val childUpdates = HashMap<String, Any>()
childUpdates["about"] = "yourNewAboutValue"
ds.ref.updateChildren(childUpdates)
}
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d(TAG, databaseError.getMessage()) //Don't ignore errors!
}
}
usersRef.addListenerForSingleValueEvent(valueEventListener)
To write a value to a node in the Firebase Realtime Database you need to know the exact path to that node. Firebase does not have the concept of SQL's update queries.
So in your case you'll need to first read the nodes you want to update, then loop over them, and then update them either one by one or with one big multi-location update statement.

Firebase real_time database query doesn't work

I'm using Firebase real-time database in my Android project. My database scheme as below :
I want to get best 10 scores. My query :
val query = reference.child("users").orderByChild("score").limitToFirst(10)
query.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {}
override fun onDataChange(dataSnapshot: DataSnapshot) {
dataSnapshot.children.forEach {
val score: String = it.child(Constants.scoreKey).value as String
Log.i(TAG,"score:"+score)
}
}
})
I hope to see best 10 scores on logcat. But it didn't. I see random scores.
What is my mistake ?
In order to solve this, change the type of your score field from String to int or long, otherwise the order is done lexicographically.
There is no operator in Firebase and as far as i know nor in other databases that allow you to change this behavior. Instead, you will need to modify the data to get the behavior you want. If you want to go with the String value, store the values that are in the order you need them when sorted lexicographically. For numbers you can accomplish that by padding them with zeroes:
As an example:
"0131" //zero added before
"0132" //zero added before
......
"1308"
"1309"
"1310"
"1311"

Categories

Resources