Firestore - Merging two queries locally
To merge 2 separate queries locally, I recommend you to use Tasks.whenAllSuccess()
method. You can achieve this, using the following lines of code:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
Query firstQuery = rootRef...
Query secondQuery = rootRef...
Task firstTask = firstQuery.get();
Task secondTask = secondQuery.get();
Task combinedTask = Tasks.whenAllSuccess(firstTask, secondTask).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
@Override
public void onSuccess(List<Object> list) {
//Do what you need to do with your list
}
});
As you can see, when overriding the onSuccess()
method the result is a list
of objects which has the exact order of the tasks that were passed as arguments into the whenAllSuccess()
method.
There is also another approach and that would be to use Tasks.continueWith()
method. But according to the use-case of your app, you can use eiter whenAllSuccess()
method or continueWith()
method. Please see here the official documentation.
Combining multiple Firestore queries to get specific results (with pagination)
Say you want to show 10 results on a page. You will need to get 10 results for each of the subqueries, and then merge the results client-side. You will be overreading quite a bit of data, but that's unfortunately unavoidable in such an implementation.
The (preferred) alternative is usually to find a data model that allows you to implement the use-case with a single query. It is impossible to say generically how to do that, but it typically involves adding a field for the OR condition.
Say you want to get all results where either "fieldA" is "Red" or "fieldB" is "Blue". By adding a field "fieldA_is_Red_or_fieldB_is_Blue", you could then perform a single query on that field. This may seem horribly contrived in this example, but in many use-cases it is more reasonable and may be a good way to implement your OR use-case with a single query.
How to merge two queries using Firestore - Swift
There are many ways to accomplish this; here's one option.
To provide an answer, we have to make a couple of additions; first, we need somewhere to store the data retrieved from firebase so here's a class to contains some chat information
class ChatClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as! String
self.to = withDoc.get("to") as! String
self.msg = withDoc.get("msg") as! String
self.timestamp = withDoc.get("timestamp") as! Int
}
}
then we need a class level array to store it so we can use it later - perhaps as a tableView dataSource
class ViewController: NSViewController {
var sortedChatArray = [ChatClass]()
The setup is we have two users, Jay and Cindy and we want to retrieve all of the chats between them and sort by timestamp (just an Int in this case).
Here's the code that reads in all of the chats from one user to another creates ChatClass objects and adds them to an array. When complete that array is passed back to the calling completion handler for further processing.
func chatQuery(from: String, to: String, completion: @escaping( [ChatClass] ) -> Void) {
let chatsColl = self.db.collection("chats") //self.db points to my Firestore
chatsColl.whereField("from", isEqualTo: from).whereField("to", isEqualTo: to).getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var chatArray = [ChatClass]()
for doc in docs {
let chat = ChatClass(withDoc: doc)
chatArray.append(chat)
}
completion(chatArray)
})
}
Then the tricky bit. The code calls the above code which returns an array The above code is called again, returning another array. The arrays are combined, sorted and printed to console.
func buildChatArray() {
self.chatQuery(from: "Jay", to: "Cindy", completion: { jayCindyArray in
self.chatQuery(from: "Cindy", to: "Jay", completion: { cindyJayArray in
let unsortedArray = jayCindyArray + cindyJayArray
self.sortedChatArray = unsortedArray.sorted(by: { $0.timestamp < $1.timestamp })
for chat in self.sortedChatArray {
print(chat.timestamp, chat.from, chat.to, chat.msg)
}
})
})
}
and the output
ts: 2 from: Cindy to: Jay msg: Hey Jay, Sup.
ts: 3 from: Jay to: Cindy msg: Hi Cindy. Not much
ts: 9 from: Jay to: Cindy msg: Talk to you later
Is it possible to merge an unknown number of firestore queries in android?
I've tested this on my current project, it works fine (tested for only two documents with a followingList.size of 2 elements)
public void testMy() {
db = FirebaseFirestore.getInstance();
List<String> followingList = new ArrayList<>();
followingList.add("tag1"); followingList.add("tag2"); followingList.add("tag3"); // ...
List<Task> taskList = new ArrayList<>();
for (String s : followingList) {
Query query = db.collection("invites")
.whereEqualTo("uid", s)
.orderBy("imageUrl_1", Query.Direction.ASCENDING);; //followingList is a list of Strings that I want to query
Task<QuerySnapshot> task = query.get();
taskList.add(task);
}
Tasks.whenAllSuccess(taskList.toArray(new Task[taskList.size()])).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
@Override
public void onSuccess(List<Object> objects) {
@SuppressWarnings("unchecked")
List<QuerySnapshot> querySnapshots = (List<QuerySnapshot>)(List<?>) objects;
for (QuerySnapshot qs : querySnapshots) {
for (QueryDocumentSnapshot qds : qs) {
Log.d(TAG, qds.getData().toString());
}
}
}
});
}
Run multiple Firestore queries and wait for all of them to be completed
You can simply merge all your 4 separate queries locally, using Tasks's whenAllSuccess() method. You can achieve this, using the following lines of code:
Task t1 = Query1.get();
Task t2 = Query2.get();
Task t3 = Query3.get();
Task t4 = Query4.get();
Task combinedTask = Tasks.whenAllSuccess(t1, t2, t3, t4).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
@Override
public void onSuccess(List<Object> list) {
//Do what you need to do with your list
}
});
As you can see, when overriding the onSuccess()
method the result is a list
of objects that you get from those queries.
The Cloud Firestore client, already runs all network operations in a background thread. This means that all operations take place without blocking your main thread. Putting it in an AsyncTask
does not give any additional benefits.
Related Topics
Best Way to Convert an Arraylist to a String
Why Does Arrayindexoutofboundsexception Occur and How to Avoid It in Android
Directory Creation Not Working in Marshmallow Android
Find the Pid of a Java Process Under Linux
Certificateexception: No Subject Alternative Names Present
Java.Lang.Unsatisfiedlinkerror in Linux
Find Java_Home and Set It on Rhel
Deserialize a List≪T≫ Object With Gson
How to Parse a Local JSON File from Assets Folder into a Listview
Android N Change Language Programmatically
Tool for Creating a Java Daemon Service on Linux
How to Set Ld_Library_Path for Java Process