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) {
}
});
Related
I have a form which insert record with multiple values for userID logged in .
Whenever I am submitting a second time form, it's updating existing record rather than inserting new record .
I need to be able to insert multiple records for same documentID(collectionID) rather than updating existing record
String uniqueID = UUID.randomUUID().toString();
DocumentReference documentReference = fStore.collection("data").document(userID);
Map<String, Object> user = new HashMap<>();
user.put("date", dates);
user.put("vehicle_number", vehicle_number);
user.put("cost", costs);
user.put("liters", liters);
user.put("fuel_type", sp);
user.put("ID", uniqueID);
documentReference.set(user).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(adding_vehicle_expense.this, "Data inserted", Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("here2" + TAG, "onFailure: " + e.toString());
}
});
Firebase
There is no way to have multiple documents directly inside another document. But it is possible to have a subcollection under a document, and then add multiple documents in that subcollection by calling add().
E.g.
documentReference.collection("userdata").add(user).addOnSuccessListener(new OnSuccessListener<Void>() {
...
I have a root node named 'Posts'. Then each node inside Posts has a unique postID generated by push(). Now each post has several children. One such child is "saves" in which I have stored the userIDs of those users who have saved this particular post. What I want to do is that I want to create and display a list of all those posts which a particular user has saved.
This means that I have to write a code that will scroll through all posts in 'Posts' node, check for 'saves' child in each post,
if it exists, then check for userID of a particular user in 'saves',
if it exists, then that post should be added to a list called 'mySavedPosts'.
Please click here to see the structure of my 'Posts' node.
I have tried many different ways to achieve this but I am just getting an empty list or app crashes. I cannot post all the different ways that I have tried since that would make this question very lengthy and clumsy, but all my ways revolve somewhere around this approach:
FirebaseDatabase.getInstance().getReference()
.child("Posts")
.child("saves")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
mySavedPosts.clear();
if (snapshot.child(FirebaseAuth.getInstance().getCurrentUser().getUid()).exists()){
for (DataSnapshot dataSnapshot : snapshot.getChildren()){
Post post = dataSnapshot.getValue(Post.class);
mySavedPosts.add(post);
}
}
Collections.reverse(mySavedPosts);
postAdapterSaves.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
I have also tried entering .child(post.getPostID()) after .child("Posts") but that generates a null Pointer Exception.
I have also tried entering .child(auth.getUid()) after .child("saves"), that didn't work too!
I have tried 10s of other modifications but none of them have worked.
According to your comment:
I just need to see if the auth.getUid() is there in the children of "saves".
To check if the UID of the authenticated user exists in the "saves" node, please use the following lines of code:
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference postsRef = rootRef.child("Posts");
Query query = postsRef.orderByChild("saves/" + uid).equalTo(true);
query.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
for (DataSnapshot postSnapshot : task.getResult().getChildren()) {
Post post = postSnapshot.getValue(Post.class);
mySavedPosts.add(post);
}
Collections.reverse(mySavedPosts);
postAdapterSaves.notifyDataSetChanged();
} else {
Log.d("TAG", task.getException().getMessage()); //Don't ignore potential errors!
}
}
});
So I am working on the memories Firebase app and there is a stage where the user can upload photos to his memory. Each photo has uniq name "ImgLink0","ImgLink1",etc..
The function I am working on is to delete a specific photo when I am pressing a long click, but I can't reach the image in Firebase.
I tried like below but I got stuck because I can't identify the image key:
mDatabase.child(userUID).child("Memories").child(memoryName).child("Images").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot imageSnapshot : dataSnapshot.getChildren()) {
String imagesKey = imageSnapshot.getKey();
String imagesValue = (String) imageSnapshot.getValue();
Log.e("Test","" + imagesKey + "" + imagesValue);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
And this is the data structure:
I tried using query too but with no success because I don't have the image key.
Thank you for any help :)
To delete a node from the database, you need to know the completely path to that node.
Assuming you do know the URL of the image to delete, but not the key (ImgLink1 or ImgLink2) that is stored under, you're going to have to use a query to look up that key.
Something like:
DatabaseReference ref = mDatabase.child(userUID).child("Memories").child(memoryName).child("Images");
Query query = ref.orderByValue().equalTo("URL of the image")
query..addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot imageSnapshot : dataSnapshot.getChildren()) {
imageSnapshot.getRef().removeValue();
}
}
...
Also see these related answer:
deleting specific post in database(by post node) but it deletes entire database_table
How can i remove all fields and values by using key value or a field key from firebase realtime database?
To remove the second URL for example, please use the following lines of code:
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference imageToDeleteRef = rootRef
.child(uid)
.child("Memories")
.child("TestMem")
.child("Images")
.child("ImgLink1");
imageToDeleteRef.removeValue().addOnCompleteListener(/* ... */);
So I have created a reference that points exactly to ImgLink1 node and then I called .removeValue() on reference in order to delete it. So there is no need to read the value in order to perform a delete operation.
If you want to read the URL, please use these lines:
imageToDeleteRef.addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
String url1 = task.getResult().child("ImgLink1").getValue(String.class);
Log.d("TAG", url1);
} else {
Log.d("TAG", task.getException().getMessage()); //Don't ignore potential errors!
}
}
});
The result in the logcat will be:
https://firebasestorage.googleapis.com...
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")
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.