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.
Related
I am writing code to get the newsfeed from the database and to show this feed in UI. This is my Firestore database structure:
users->uniqueUserId |-->UsersProfileInfo--->Profile(document)
|-->FeedNewsFeed |--->unique documents for each newsfeed
|--->unique documents for each newsfeed
|--->unique documents for each newsfeed
to get each news feed from every user in my user collection I have to write nested for loops which takes some extra time and getting data from Firestore also takes some times, so is there any nice way to optimize this problem.
Function for getting newsfeed
suspend fun getAllNewsFeeds(): ArrayList<NewsFeedClass> {
list.clear()
val querySnapshot = collectionRef.get().await()
//Traversing through each document in collection
for (document in querySnapshot) {
val currDocRef = document.reference
//Getting user name
val userName =
currDocRef
.collection(Constants.UserProfileInfo)
.document(Constants.Profile)
.get()
.await()
.toObject(FeedUserName::class.java)?.userName
// adding username in newsFeedClass for displaying on newsfeed
val newsFeedClass = NewsFeedClass()
if (userName != null) {
newsFeedClass.username = userName
}
//getting QuerySnapshot from FeedNewsFeed collection
val newsFeedQuerySnapshot =
currDocRef
.collection(Constants.FeedNewsFeeds).get().await()
//Traversing through each document in
// collecting and respectively adding feed on newsFeedClass
//adding newsFeedClass to list
for (documentOfFeed in newsFeedQuerySnapshot) {
val thisDocRef = documentOfFeed.reference
val feed =
thisDocRef
.get().await().toObject<FeedNewsFeed>()
if (feed != null) {
newsFeedClass.content = feed.newsfeed
list.add(NewsFeedClass(newsFeedClass.username, newsFeedClass.content))
}
}
}
list.shuffle()
return list
}
What you're experiencing in your code is the expected behavior. Why? Because at every iteration of your loop, you are reading data from Firestore using get() and right after that you call await(). This means that all the operations run one after another. It basically means, that at each iteration of your loop, you wait until you get the data from the database. The more iterations you have, the longer it will take. So these operations run sequentially, and not in parallel as you probably might think.
If you need to get the data in parallel you can add the kotlinx-coroutines-play-services library to your project and use the asDeferred extension function that converts a Task into a Deferred object. In the end, you can call the awaitAll() extension function that will wait while all Firestore read operations are loaded in parallel. So in code, it should look like this:
val tasks: MutableList<Deferred<DocumentSnapshot>> = mutableListOf()
for (document in querySnapshot) {
val currDocRef = document.reference
val deferredTask = currDocRef
.collection(Constants.UserProfileInfo)
.document(Constants.Profile)
.get()
.asDeferred()
tasks.add(deferredTask)
}
tasks.awaitAll().forEach { document ->
//Do what you need to do with your documents.
}
What I mean by using the collection is this:
val db = FirebaseFirestore.getInstance()
db.collection(Constants.REDEEM_CODE)
.whereEqualTo("key" , key)
.update("x" , 0) // this line is poping an error because there is no such method
I don't know the name of the document all I know is that documents have a field that is key and every document in that collection has a different key. That's the reason I'm trying to filter the documents using the where equal to method but now I don't know how to update the x field in that same document as I don't know the document file name ( which is set to auto-generate)
To update the field x with the value of 0 inside each document that is returned by your query, please use the following lines of code:
val db = FirebaseFirestore.getInstance()
val codeRef = db.collection(Constants.REDEEM_CODE)
codeRef.whereEqualTo("key" , key).get().addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
val update: MutableMap<String, Any> = HashMap()
update["x"] = 0
codeRef.document(document.id).set(update, SetOptions.merge())
}
}
}
You cannot query the database and update the documents in a single go. You need to query the collection, get the documents, and right after that perform the update.
P.S. You are getting that error because update() method is not a function that exists inside the Query class, but inside DocumentReference class.
I'm trying to get all my posts which time is newer ones first, I have made this query that should bring to me all the newest posts that I uploaded or other people did.
Thing is that I'm getting posts shuffled with timestamps , there are all mixed up instead of ordered by new ones
If I do the same query from the firebase console they are ordered the way it should
My recyclerview does NOT have any reverselayout or stackfromend attributes and I'm not expecting to use them, instead I just want my list to come from firebase ordered
#ExperimentalCoroutinesApi
suspend fun getLatestPosts(): Flow<Result<List<Post>>> = callbackFlow {
val postList = mutableListOf<Post>()
// Reference to use in Firestore
var eventsCollection: CollectionReference? = null
try {
eventsCollection = firestore.collection("posts")
eventsCollection.orderBy("created_at", Query.Direction.DESCENDING)
} catch (e: Throwable) {
// If Firebase cannot be initialized, close the stream of data
// flow consumers will stop collecting and the coroutine will resume
close(e)
}
val suscription = eventsCollection?.addSnapshotListener { value, error ->
if (value == null) {
return#addSnapshotListener
}
try {
postList.clear()
for (post in value.documents) {
post.toObject(Post::class.java)?.let { fbPost ->
fbPost.apply {
created_at = post.getTimestamp(
"created_at",
DocumentSnapshot.ServerTimestampBehavior.ESTIMATE
)?.toDate()
}
postList.add(fbPost)
}
}
offer(Result.Success(postList))
} catch (e: Exception) {
close(e)
}
}
awaitClose { suscription?.remove() }
}
Now if I sort the list locally after getting the data that works, but I don't want it to be client side, I want to have an ordered list from the server.
What I'm doing wrong ?
Posts timestamp are saved with #ServerTimestamp and Date format into Firestore
CollectionReference (Query) objects are immutable - they can't be changed once created. They use a builder type pattern to construct new Query objects by adding constraints. The original Query remains unmodified.
If you want to compose a set of operations to perform on a Query, you would have to remember the new Query returned by each operation. An easy way to do this is by reassigning the prior Query if you no longer need it:
var eventsQuery: Query = firestore.collection("posts")
eventsQuery = eventsQuery.orderBy("created_at", Query.Direction.DESCENDING)
Or you can do it as a chained sequence of operations, which is more idiomatic in the case that you know all the operations ahead of time:
val eventsQuery = firestore
.collection("posts")
.orderBy("created_at", Query.Direction.DESCENDING)
If you need to conditionally apply operations, however, you will need to take the first approach. A more detailed explanation can be found here: Firestore: Multiple conditional where clauses
Problem was at this line
eventsCollection = firestore.collection("posts")
eventsCollection.orderBy("created_at", Query.Direction.DESCENDING)
Seems like I need to wrap it up all into 1 line and make it a Query, it does not apply any changes to the eventsCollection, this can be done changing CollectionReference to Query
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.
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.