Firebase, .addSnapshotListener on all the nodes - java

I am using FireStore for my Spring-boot project and i made this code work fine. With that i can
get real time updates on what values are changing inside the collection "values".
Firestore firestore = FirestoreClient.getFirestore();
CollectionReference valuesCollectionRef = firestore.collection("Users").document("user_name").collection("sensors")
.document("sensorX").collection("Values");
valuesCollectionRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(QuerySnapshot snapshot, FirestoreException error) {
System.out.println(snapshot.getDocumentChanges()
.get(0).getDocument().get("value"));
}
});
What I want is a listener on the "Users" collection which gets triggered when a child is added anywhere in the database, in "sensorX" or "sensorY" and in different documents of "Users" collection.
Is that possible with Firestore in Java ?

Listeners in Firestore are shallow. They listen on a single collection, or (when using a collection group query) on multiple collections with the same name (which presumably have the same data structure). It is not possible to have a listener for a collection and all its subcollections.
But if you want to know whenever anyone's sensors collection is modified, a collection group query is what you want.
This example (slightly modified from the documentation) listens for any writes to the sensors collection of any user (or any collection named sensors elsewhere in the database):
db.collectionGroup("sensors").get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
// ...
}
});
If you'd like to listen to any values of any sensors of any users, you could equally use:
db.collectionGroup("Values").get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
// ...
}
});
The thing to be most away of with that last one is that Values is a fairly generic name for a collection, and this will listen to any Values collection no matter where it exists in the database. When you're using collection group queries, you'll typically want to use fairly distinctive collection names, such as for example SensorValues.

Related

How can I perform OR query while searching in firebase?

Here are the various attributes of a person.
I want to implement a search where the results come if any of the fields: specializationField, hospitalName or fullName have the same letters.
For example if I search 'sh', this person should appear in the field, because of the similar hospital name.
This is the code I am using to search only for fullName:
FirebaseRecyclerOptions<DoctorHelperClass> options =
new FirebaseRecyclerOptions.Builder<DoctorHelperClass>()
.setQuery(FirebaseDatabase.getInstance().getReference().child("Doctor").orderByChild("fullName").startAt(s.toUpperCase()).endAt(s.toLowerCase()+"\uf8ff"), DoctorHelperClass.class)
.build();
adapter = new DoctorsAdapters(options, FindDoctorActivity.this);
adapter.startListening();
binding.rvListDoctors.setAdapter(adapter);
Please help me out
As #Puf said, you can't achieve it at Firebase Realtime Database but you can do it at client side which mean at the Android part.
First, you cannot use FirebaseUI which is you are currently using, instead you need to use https://firebase.google.com/docs/database/android/read-and-write#read_data
ValueEventListener postListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// You have to make for each loop
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
DoctorHelperClass doc = snapshot.getValue(DoctorHelperClass.class);
//List them in an array
docList.add(doc);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
};
mPostReference.addValueEventListener(postListener);
Once you have added all the list of doctors. You can compare them using the arrayList.
You can do something like this.
private void searchDoc(final String inputDoc){
boolean isFound = false;
for (DoctorHelperClass doc in docList){
if (doc.getFullName() == inputDoc && doc.getHospitalName() == inputDoc){
isFound = true;
//Do something if found
}
}
}
I hope you get the concept of it.
There is no support for OR conditions in Firebase Realtime Database. You will either have to perform multiple queries and merge the results client-side, or create a specialized field for performing this search.
But given your question, you may be looking for text search capabilities that are well beyond what Firebase Realtime Database handles. Instead of trying to shoehorn those requirements onto Firebase, I recommend using an additional (or even other) database for meeting your text search requirements.
Also see:
Use firebase realtime database create search function
How to search anywhere in string in Firebase Database - Android
Searching in Firebase without server side code
Firebase and indexing/search

How to get in order data from two different children in Firebase Realtime Database?

I have two models photos and videos. To retrieve photos I call an addChildEventListener and to get videos I call another addChildEventListener added.
Code example
databaseReference = FirebaseDatabase.getInstance().getReference("videos");
Query queryContent= databaseReference;
queryContent.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
videos v= dataSnapshot.getValue(videos.class);
objectItems.add(v);
loading = true;
contentViewPager.setAdapter(new discover_fullscreen_adapter(getApplicationContext(), objectItems));
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
For photos is the same, I just change the reference, and the model
databaseReference = FirebaseDatabase.getInstance().getReference("photos");
photos p = dataSnapshot.getValue(photos.class);
objectItems.add(p)
First I add the videos and then the photos and the order is messy because I want to get videos and photos together in the order they were taken. Like a phone gallery. We have photos and videos ordered in the way they were taken (timestamp in his case). How can I achieve the same knowing that photos and videos are in different nodes and they are called by different models?
How to do it with Firebase Realtime Database
My models are based on getter and setter, I don't want to make the question bigger.
Thank you
I want to get videos and photos together in the order they were taken. Like a phone gallery.
You can perform a Firebase Realtime Database query only on a single node. You cannot get data across multiple nodes using a Query. If you want to get the "photos", as well as the "videos" in a single go, then both should exist within the same node. So you should create another node called "photosAndVideos" where you should add all the data. This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you see this video, Denormalization is normal with the Firebase Database.
Once you have all data under a single node, you can then perform the desired query according to a timestamp. Please see my answer from the following post:
How to save the current date/time when I add new value to Firebase Realtime Database
To see how to add a timestamp property to your object. By default Firebase orders the results ascending. However, if you need a descending order, please see my answer from the following post:
How to arrange firebase database data in ascending or descending order?
Edit:
You have to check each object from the results an instance of which class is. So when you read the data, you cannot only cast the value. You'll have to read each object and request the correct class in the call to getValue().

Is there any way to only proceed once I have obtained data from Firebase in Android?

I am working on an app for a hotel, which enables hotel management to report and view concerns and issues. I am using Android and Firebase for this app.
Here is the database structure of a reported concern:
To minimize data download and optimize speed, I am adding "Active" and "Resolved" nodes in the database, like below:
Now, the hotel wants me to add the function to create an Excel report of concerns closed/resolved within the past month. For this, I will be attaching a Single Value Event Listener on the "resolved" node, get keys of resolved concerns, then for each key, fetch data from "allConcerns" node, store each node's data into an ArrayList of String. After which I will use this JSON to Excel API for Android to create Excel file.
I am able to access keys of resolved concerns with this code:
DatabaseReference resolvedReference = FirebaseDatabase.getInstance().getReference()
.child(getApplicationContext().getResources().getString(R.string.concerns))
.child(getApplicationContext().getResources().getString(R.string.resolved));
final ArrayList<String> keys = new ArrayList<>();
resolvedReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
//Getting keys of all resolved concerns in keys arraylist here
for (DataSnapshot ds : snapshot.getChildren()){
keys.add(ds.getValue(String.class));
}
//Storing JSON data in this arraylist
final ArrayList<String> data = new ArrayList<>();
for(int i = 0; i<keys.size() ; ++i){
String key = keys.get(i);
//Getting data of each concern here
FirebaseDatabase.getInstance().getReference().child(getApplicationContext().getResources().getString(R.string.allConcerns))
.child(key).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
String type = snapshot.child("type").getValue().toString();
Log.i("Type", type);
if(type.equals("0")) {
SafetyConcernClass s = snapshot.getValue(SafetyConcernClass.class);
Log.i("Snapshot of key", s.toString());
data.add(s.toString());
}
else{
GembaWalkClass g = snapshot.getValue(GembaWalkClass.class);
Log.i("Snapshot of key", g.toString());
data.add(g.toString());
}
Proof proof = snapshot.child("proof").getValue(Proof.class);
Log.i("Proof", proof.toString());
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
//Issue I am facing is here
Log.i("Data size", String.valueOf(data.size()));
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
The real issue here is while logging data.size(). Since Firebase is asynchronous, FOR loop ends before data is fetched and entered into the data ArrayList, hence it gives me a size of 0. And since no data is fetched, I can't create an Excel file.
My question is, how can I make sure I am proceeding to log data.size() ONLY after data of respective resolved concerns is stored in the ArrayList?
The typical approach is to keep a counter or a countdown latch to track how many of the concern snapshots you've already downloaded. Once the counter reaches keys.size() you know that you're done.
Also see Setting Singleton property value in Firebase Listener
You should write your method
addListenerForSingleValueEvent
using AsyncTask or Kotlin coroutines
and in onPostExecute() of AsyncTask, you can proceed to further action.

How to query a document field inside of queryDocumentSnapshot Firestore

I am building a chatroom application and am trying to query all messages then separate them accordingly based on the message sender.
This is what my Firestore architecture looks like:
And my code so far:
CollectionReference chatRoomMsgs = db.collection("chatrooms").document(chatRoomID).collection("Messages");
chatRoomMsgs.get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for(QueryDocumentSnapshot documentSnapshot: queryDocumentSnapshots){
if(documentSnapshot.get("sentby") == firebaseUser.getUid()){
}
}
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
What I am (currently) trying to do is pull ALL chatroom messages first, and then separate them out in onSuccess.
I am trying to say "ok if the message was sent by this user, grab the image field value of that same document and add it to an array so the image can be accessed later, and if the message was not sent by the same user, also grab the image url but add it to a different array"
How can I do this? Thanks!
Update
I tried adding the while loop below to get some sort of output, wasn't triggering
ArrayList<String> sentPics = new ArrayList<String>();
while(documentSnapshot.get("sentby") == firebaseUser.getUid()){
sentPics.add(documentSnapshot.get("image").toString());
Log.d("PICLIST", sentPics.toString());
}
If you want to get all the messages sent by a specific user, then you should use the following query:
CollectionReference chatRoomMsgs = db.collection("chatrooms").document(chatRoomID).collection("Messages");
Query sendByQuery = chatRoomMsgs.whereEqualTo("sentby", firebaseUser.getUid());
sendByQuery.addOnSuccessListener(/* ... */);
Using this solution you'll significantly reduce the number of read operations as you get as a result only the messages that correspond to the logged-in user.
Your solution is very expensive because you are getting all messages that exist in the Messages collection and you filter them on the client. If you have a total of 100 messages in the collection but only 10 correspond to the logged-in user, you'll be charged with 100 reads. In my solution, you'll only be charged with 10, as the query only 10 messages returns.
If want to see another approach, here you can find a tutorial on how to create a complete and functional Firestore Chat App.
What you need to do is make a POJO named Message that maps to your Messages collection with member variables image and sentby and convert the documentSnapshot to a Message object using:
Message message = documentSnapshot.toObject(Message.class)
From there on, you can just use the getters to achieve what you want.
Hope it helps!

Getting LiveData values to use with other LiveData values

(New to android programming)
I have a RecyclerView of subcategories and their corresponding keywords.
like this:
https://imgur.com/a/bEFS6cm
The subcategories are fetched by observing
subcategoryViewModel.getAllSubcategoriesForCategory(id).observe...
(I am using Room), here I have the id available (it is known which category is chosen at the time of creation of the subcategory fragment).
However, I am having troubles calculating the corresponding keywords. The keywords are the names of the articles contained in each subcategory.
In my ArticleDao I have a function
LiveData<List<String>> getAllArticleNamesById(int subId);
So logically, I just have to get the current subcategory list and get their corresponding keywords in a for loop.
But how do I do that if both the subcategory list and the keywords list are LiveData and I cannot access their values, only their observers can?
I tried putting an observer within an observer but I don't think that's the best idea.
subcategoryViewModel.getAllSubcategoriesForCategory(id).observe(getViewLifecycleOwner(),
new Observer<List<Subcategory>>() {
#Override
public void onChanged(List<Subcategory> subcategories) {
recyclerAdapter.setSubcategories(subcategories);
for (Subcategory sub : subcategories) {
articleViewModel.getAllArticleNamesById(sub.getId()).observe(getViewLifecycleOwner(),
new Observer<List<String>>() {
#Override
public void onChanged(List<String> strings) {
recyclerAdapter.addToKeywordsList(keywordsIntoString(strings));
}
});
}
});
I found some information on LiveData Transformations (map, switchmap) but that doesn't really apply to my problem since its supposed to apply a function on LiveData when otherLiveData changes. (at least from what I understand). I just need to access the current subcategory list and work with the values so I can observe the article names.
Ideally, I would need something like this:
for (Subcategory sub : subcategoryList) {
articleViewModel.getAllArticleNamesById(sub.getId()).observe...
}
So my question is, how do I access the subcategories which I'm already observing? Am I missing something?

Categories