How to handle errors during nested callbacks in Firebase Realtime Database? - java

I'm using Firebase Realtime Database and I need to do multiple operations in the database, some of that depending on the result of the previous, creating the famous 'callback hell'. How can i handle errors when, for example, the second call goes wrong, but the first one succeeded ?
I tried to find some "Firebase realtime database transaction" (like transactions in mysql or postgres), but didn't find any good examples.
mDb.getReference("users").setValue("someValue").addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
mDb.getReference("services").setValue("someValue2").addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
mDb.getReference("stores").setValue("someValue3").addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// Here, for some reason, the value "someValue3" could not be set.
}
});
}
}
});
}
}
});
I need that, if some operation goes wrong, to revert ('rollback') the values set before. Right now, if the transaction on reference 'stores' fail, the values set on 'users' and 'services' will keep on the database.

If you want to update multiple values in the database in one call, use a multi-location update that André mentioned in his comment.
With a multi-location update, your entire code can be reduced to:
Map<String, Object> values = new HashMap<>();
values.put("users", "someValue");
values.put("services", "someValue2");
values.put("stores", "someValue3");
mDb.updateChildren(values).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// All writes either completed, or none happened
}
});
Some notes:
The keys in the values map can contain entire paths, so if you want to update the name of a specific user it could be values.put("users/uidOfTheUser/name", "new name").
If you need to write a new value based on the existing value of a node, you will need to use transactions instead. But note that transactions across multiple top-level nodes tend to be highly contentious, so I'd recommend trying to stay away from them in your current use-case.

Related

Firestore doesn't return results in requested order

I have some Firestore requests that I try to get in a for loop, but because Firebase queries are running Async, the results return in random order. Do you have any way to fix it? My code is below.
Thank you in advance!
for(Feed feed: feedList){
tasks.add(db.document(feed.getMarker().getPath()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
myMarker.add(task.getResult().toObject(SavedMarker.class));
System.out.println("Marker: "+ Objects.requireNonNull(task.getResult().toObject(SavedMarker.class)).getDescription());
System.out.println("Marker: "+task.getResult().getId());
}
}));
tasks.add(db.document(feed.getUser().getPath()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
myUser.add(task.getResult().toObject(Users.class));
}
}));
}
Tasks.whenAllSuccess(tasks).addOnCompleteListener(new OnCompleteListener<List<Object>>() {
#Override
public void onComplete(#NonNull Task<List<Object>> task) {
//Do Stuff
}
For Example:
IDX Gives ResultX
In my feedList I have saved 4 ids like:
ID1
ID2
ID3
ID4
But when i try to receive their results with the use of a loop i get:
Result2
Result1
Result3
Result4
The order is usually random.
because Firebase queries are running Async, the results return in random order.
The whenAllSuccess() method from the Tasks class will always provide the documents from the tasks right into the callback in a List<Object>. The order is the same as the order in which the tasks were added to the whenAllSuccess() method. However, if you need an order other than that, then you should either order them on the client in the way you want or create a query based on a field and order the documents as needed.

Continue insert new data in existing array in Firebase Database from Android

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) {
}
});

How Should I fetch the document fields and use them in another map for another collection?

How should I fetch the document fields from one collection and combine them to add a new document to another collection? I have attached picture of the database how does it looks, I want to fetch the fields from the collection show and want to update it to the new collection along with some other data:
private void savePost(String mPostTitle, String mPostContent, String mlistSpinnerC) {
final DocumentReference docRef = FirebaseFirestore.getInstance().collection("users").document(mauth.getCurrentUser().getUid());
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document != null) {
String username = (String)
document.get("username");
String email= (String) document.get(email);
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
postMap.put(Constants.POSTTTITLE, mPostTitle);
postMap.put(Constants.POSTCATEGORY, mlistSpinnerC);
postMap.put(Constants.POSTCONTENT, mPostContent);
postMap.put(Constants.TIMESTAMP, (System.currentTimeMillis()/1000));
postMap.put(Constants.USER_ID,mauth.getCurrentUser().getUid());
postMap.put("username", username);
PostsRef.document().set(postMap).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
Intent toHomeActivity = new Intent(AddPostActivity.this, MainActivity.class);
startActivity(toHomeActivity);
}
}
});
I am just not able to map the fields from one collection to another collection, please guide me the correct method to that.
By the time you are trying to add the username to your postMap using the following line of code:
postMap.put("username", username);
The data has not finished loading yet from the database and this is because the listener you have added to your get() call is being invoked some unknown amount of time later after your query finishes. You don't know how long it's going to take, it may take from a few hundred milliseconds to a few seconds before that data is available. The onComplete() method has an asynchronous behavior, that's why you cannot get that username in such a way.
A quick solve for this problem would be to move all that block of code related to adding data to the postMap, inside the onComplete() method. In this you are waiting for the callback and username your will be available. Otherwise I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

How to fix Android Cloud Firestore document snapshot

I am checking if the user has made an appointment in the database if not add the user(add a new document which has the user details). The problem with my app is that it runs both AddUser() and AlertUser() functions:
DocumentReference docRef = firebaseFirestore.collection(group).document(userIdentity);
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
DocumentSnapshot documentSnapshot = task.getResult();
if (documentSnapshot != null) {
if (!documentSnapshot.exists()){
//User does not exist create the new user
addUser();
} else {
//User has already made an appointment show dialog
AlertUser();
}
}
}else {
Log.i("Key","Task not successfull");
}
}
});
What does this code do is checking whether a document with the userIdentity id actually exists. If it doesn't exist, the addUser() method is called, otherwise, the AlertUser() is called. There is no way in which both parts of the if statement are evaluated. So it's one or the other. You can have both method calls only if you access all those lines of code twice. Meaning that the first time the user is created and the second time is alerted. To solve this, remove from your code the part where you are calling the above code twice.
This is a Cloud Firestore question and not a Firebase Realtime database one, so I've changed the tag accordingly.

How to retrieve all documents from a collection in Firestore?

I want to show all the documents that are in a collection using ListView in my app. For this, I need to retrieve all the documents, right? How can I do this? I've searched a lot but couldn't find anything in Google Docs / any proper solution.
Be careful, as each document returned counts as a 'read'. Make sure you've read the pricing section of firestore. If you had 10,000 documents in this collection, every time you made this query you'd be using 10,000 reads.
Nevertheless, the query is:
FirebaseFirestore.getInstance()
.collection("collectionname")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<DocumentSnapshot> myListOfDocuments = task.getResult().getDocuments();
}
}
});

Categories

Resources