I have a list of objects, saved by unique ids. Every time, I read several objects(decided by few conditions). That is done with for loop, using snapshot.getChildren(). My problem is how to continue reading the data from last retrieved object. Eg. If I have 10 objects in the database, I read 3 of them first time. Next time the app should continue reading from the fourth object, without going through already retrieved objects.
I researched about Query, startAt(), but it is allowing only search by value, not by id(my ids are firebaseReference.push()).
Code which I am using to point at specific value, but is not working:
public void getFeedPosts(final FeedPostsCallback feedPostCallback) {
final ArrayList<Post> posts = new ArrayList<>();
readFollowing = true;
getFollowing(myID, new FollowingCallback() {
#Override
public void onCallback(final ArrayList<String> following) {
referenceOnPosts().startAt("-MMequ1jrR1m73cKfgbt", "-MMequ1jrR1m73cKfgbt").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
try {
if (readFeedPosts) {
readFeedPosts = false;
Log.d(tag, "Reading feed posts");
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
Log.d(tag, dataSnapshot.toString());
posts.add(dataSnapshot.getValue(Post.class));
}
Collections.reverse(posts);
feedPostCallback.onCallback(posts);
}
} catch (Exception e) {
Log.d(tag, e.toString());
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.d(tag, error.getMessage());
}
});
}
});
}
Here is the image:
Cursors in Firebase Realtime Database are based on being able to quickly find the anchor node in the list of potential results.
To do that you need to know up to two values:
You need to know the value of the property you sorted on, for the node you want to start at.
You may need to know the key of the node you want to start at, in case there may be more nodes with the same value for #1.
If you have both values, you pass them into startAt() (or endAt() if you want the final slice of the results):
ref.orderBy("propertyToOrderOn").startAt("valueOfProperty", "keyOfNode")
If you want to order/paginate on the key only, you can do:
ref.orderByKey().startAt("-MMequ1jrR1m73cKfgbt")
Related
In Firebase Cloud Functions (Javascript), we use database trigger like below,
functions.database.ref('/DoctorApp/DrTimeSlot/{doctorUID}/Patients/{patientID}')
.onCreate((snapshot, context) => {
// codes here
};
Here, {doctorUID} is not known so all function is working on all UIDs.
Is this possible to use the same method in Android Studio (Java)? below this code is not working for me, can you provide me the alternate of the code.
Query query = rootData.child("DrTimeSlot/{drUID}/Patients/").orderByChild("patientUID").equalTo(auth.getUid());
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
//codes here
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
While not possible to use the same syntax as Cloud Functions, your query to find all doctors with a given patient ID can be achieved using:
Query query = rootData.child("DrTimeSlot").orderByChild("Patients/" + auth.getUid()).startAt(""); // startAt("") will exclude non-existent values
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot queryDataSnapshot) {
if (queryDataSnapshot.hasChildren()) {
// handle no results
System.out.println("No results returned");
return;
}
for (DataSnapshot doctorDataSnapshot: dataSnapshot.getChildren()) {
// TODO: handle the data
System.out.println(doctorDataSnapshot.getKey() + " => " + doctorDataSnapshot.getValue());
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
// Getting doctors failed, log a message
Log.w(TAG, "findDoctorsForUser:onCancelled", databaseError.toException());
}
});
However, to permit such an operation, the client must have read access to every single doctor's data, even if the user performing the search is not a patient of that doctor. It will also be entirely processed on the client device which means downloading all of the data under "DrTimeSlot" which is not recommended and is also a massive security & privacy risk.
Instead, in the user data of a particular user, store a list of their doctors. When you need to find them, download /users/userId1/doctors and then get the needed patient data from each doctor restricting read/write access to that doctor and that user only.
"users": {
"userId1": {
"doctors": {
"doctorId1": true, // boolean value for simplicity
"doctorId2": "paediatrician", // could also use value for type
"doctorId3": 10, // or number of visits
"doctorId4": false, // or if they are an active patient of this doctor
},
...
},
...
}
In android you cannot do this query child("DrTimeSlot/{drUID}/Patients/"), all 3 nodes need to be known.
If you don't know the drUID, then when you stored it first to the database, you can store it to a variable and pass the variable around, example:
String drUID = ref.push().getKey();
Then you can pass the above variable using intent to different activities.
I'm just starting to learn android studio and I'm working on a location-based project to insert location information into Firebase. I followed tutorial from youtube [https://www.youtube.com/watch?v=CwxdfamaCrk], however in the video only shows insert data from the code which is like this;
infectedArea = new ArrayList<>();
infectedArea.add(new LatLng(2.2258162, 102.4497224));
infectedArea.add(new LatLng(2.2252313, 102.4563797));
infectedArea.add(new LatLng(2.2261818, 102.4551067));
infectedArea.add(new LatLng(2.275295,102.444035));
FirebaseDatabase.getInstance()
.getReference("InfectedArea")
.child("Location")
.setValue(infectedArea)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Toast.makeText(MapsActivity.this, "Updated!", Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(MapsActivity.this, ""+e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
Below are the snapshot of my firebase.
The one with index 0, 1, 2 and 3 are inserted from the code. Now I'm trying to continue inserting data from an input form but random key was generated and new geofence cannot be created. Is there anyway to continue inserting data with hard key?
I was thinking on removing the hard key and just use the generated key but then I have no idea how to alter the code to create multiple geofence.
Firebase intentionally doesn't offer an operation for inserting items with a sequential numeric key. See:
the documentation on structuring data
The classic blog post on best practices when using arrays in Firebase
How to create auto incremented key in Firebase?
That said, you can use array-like structures with sequential numerical indexes, and you can use auto-ids. So let's look at each in turn.
Using sequential numerical indexes
To add a new item with key 4 to your current structure, you will need to:
Determine the highest key
Add a child node with one key higher
In its simplest format, that looks like this (on Android):
FirebaseDatabase.getInstance()
.getReference("InfectedArea")
.child("Location")
.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
String lastKey = "-1";
for (MutableData child: mutableData.getChildren) {
lastKey = child.getKey();
}
int nextKey = Integer.parseInt(lastKey) + 1;
mutableData.child("" + nextKey).setValue("your next value here");
// Set value and report transaction success
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "Transaction:onComplete:" + databaseError);
}
});
As you can see that is quite a lot of code. This is largely needed because multiple users may be accessing the same location at almost the same time, and we need to handle this. Firebase uses optimistic locking, but the above may still be come a serious bottleneck when there are multiple users. Plus: this is a lot more complex than your simple push().setValue(...).
Use auto-ids for your initial set of data, and for new data
You can easily write all points with push IDs (those are the keys that push() generates), once you realize that calling only push() doesn't yet write to the database. You can get a new push ID in pure Android code with:
String pushID = ref.push().getKey();
Knowing this, we can change your code to insert the initial locations to:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
// It doesn't matter what the reference points to, as push IDs are statistically guaranteed to be unique, independent of their location.
Map<String, Object> values = new HashMap<>();
infectedArea.put(ref.push().getKey(), new LatLng(2.2258162, 102.4497224));
infectedArea.put(ref.push().getKey(), new LatLng(2.2252313, 102.4563797));
infectedArea.put(ref.push().getKey(), new LatLng(2.2261818, 102.4551067));
infectedArea.put(ref.push().getKey(), new LatLng(2.275295,102.444035));
FirebaseDatabase.getInstance()
.getReference("InfectedArea")
.child("Location")
.setValue(values)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Toast.makeText(MapsActivity.this, "Updated!", Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(MapsActivity.this, ""+e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
This will result in a similar structure as you know have, but then with all keys being push IDs.
it will stockpile at HashMap, so you can getkey to get random key, and you get random key you can insert data
DatabaseReference database = FirebaseDatabase.getInstance().getReference("InfectedArea").child("Location");
database_course.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for(DataSnapshot ds :dataSnapshot.getChildren()){
String key = ds.getKey();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
I am from PHP and MySQL background and I haven't worked with JSON based DB like Firebase.
I am looking for sample code to insert data in firebase "Realtime database". I am already done with authentication stage.
To insert some data in your Firebase Database, you have to set the reference to the node you want to insert the data to and then use setValue() method.
Suppose you want to change age of the admins node, in your database in the question.
In code it looks something like this:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("users").child("admins");
ref.child("age").setValue(76);
The above code will replace 42 with 76, in your admins node's age child.
Read more about this here.
Getting data from Firebase Database is a bit more work, as you have to use listeners for that. There are 3 different event listeners at your disposal, that are valueEventListener childEventListener and singleValueEventListener.
These three eventListeners have different properties and you can use them as you like.
Suppose you want to retrieve age of your admins node from your database, then you may use a code like this to help. Note ref in this code is same as in above code.
ref.addSingleValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int age = dataSnapshot.child("age").getValue(String.class);
// this will store value of age from database to the variable age
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.d("TAG:", "Couldn't read data ", error.toException());
}
});
Read more about this here.
Refer to this link, https://firebase.google.com/docs/database/android/start/
Write a message to the database
// Write a message to the database
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("message");
myRef.setValue("Hello, World!");
Read a message from the database
// Read from the database
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
String value = dataSnapshot.getValue(String.class);
Log.d(TAG, "Value is: " + value);
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w(TAG, "Failed to read value.", error.toException());
}
});
I have a database like this Shop > warehouse > categories > cat1,cat2,.. and every category has a name and its documents. I just want only the categories, and I tried with this code:
firestore.collection("shop/warehouse").addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#javax.annotation.Nullable QuerySnapshot queryDocumentSnapshots, #javax.annotation.Nullable FirebaseFirestoreException e) {
if(queryDocumentSnapshots != null) {
for (DocumentSnapshot doc : queryDocumentSnapshots) {
String item = doc.getId();
Log.d("TEST ITEM", item);
}
} else Log.d(TAG, "queryDocumentSnapshots is null.");
}
});
But as expected I got this error:
Invalid collection reference. Collection references must have an odd number of segments, but shop/warehouse has 2
So I googled and found a solution that suggests to divide in collection/document and I wrote this:
firestore.collection("shop/").document("warehouse").addSnapshotListener(new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#javax.annotation.Nullable DocumentSnapshot documentSnapshot, #javax.annotation.Nullable FirebaseFirestoreException e) {
Map<String, Object> map = documentSnapshot.getData();
Log.d("size", map.keySet().size()+"");
}
But this keyset (I assumed they're the category names) is empty. How can I do it? Have mercy on me, it's my first time with Firebase!
To solve this, please change the following line of code:
firestore.collection("shop/").document("warehouse").addSnapshotListener(/* ... */);
to
firestore.collection("shop").document("warehouse").addSnapshotListener(/* ... */);
There is no need for that /. You can use a reference like this, "shop/warehouse" only in Firebase Realtime database where all objects all called childs and you can chain all childs in a single path. It's a more elegant way but unfortunately is not permited in Cloud Firestore.
There's no API to just list out the document IDs that exist in a collection. You will have to query and read all of the documents in the collection just to get the IDs.
This question already has answers here:
Firebase DB: Check if node has no children?
(2 answers)
Closed 5 years ago.
I am new to firebase and android. I am trying to develop a chat app for android by using firebase.
For fetching the chats I’m using below firebase query. It’s retrieving all the messages, and working fine if the chatId exist.
mChatQuery = mFirebaseChatRef.child(chatId).orderByChild("chatId").equalTo(chatId);
mChatQuery.addChildEventListener(ChatActivity.this);
The problem I’m facing is query is not returning any value if the chatId not exist.
My question is how to identify the value is null, without using another query to check the chatId isExist?
Try this:
DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child(chatId).orderByChild("chatId").equalTo(chatId);
ref.addValueEventListener(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()){
//check if chatid is in the db
for(DataSnapshot data : dataSnapshot.getChildren()){
// retrieve also
}
}
else{
//does not exists, do something here
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
You can use the above query, to retrieve the value and to check if it exists(one query) in the db. But if(dataSnasphot.exists()) only works with valueventlistener and not childeventlistener
ChildEvent listeners can not listen to nodes which don't exist, ValueEvent Listeners can.
What you can do is, have the same query attached to a ValueEventListener.
Now see here's the trick, a firebase node can never exist with zero children.
In the onDataChange() method of the value event listener, you can have a check on the children count as displayed above and you should have the check for whether the node exists or not.
mChatQuery = mFirebaseChatRef.child(chatId).orderByChild("chatId").equalTo(chatId);
mChatQuery.addValueEventListener((new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.getChildrenCount()==0){
//This indicates that the chatId doesn't exist
}else{
//Do what you want with the snapshot
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});