How to handle asynchronous Database with Firebase? - java

I have a lot of questions about handeling asynchronous database in my Android app.
Since I know that database is asynchronous, I've tried several things to handle it. As you can see in my code, I've two functions who need to use an array in my database. My first function (setArrayfromDatabase) will apply changes on my array in my database and my second function (setAnotherArray)need to use this array with changes applied from my first function. Here's my code :
FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myReff =database.getReference("server").child("user");
myReff.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Take the array from my database
final ArrayList<Integer> array_from_database;
GenericTypeIndicator<ArrayList<Integer>> genericTypeIndicator = new GenericTypeIndicator<ArrayList<Integer>>() {};
array_from_database = dataSnapshot.getValue(genericTypeIndicator) ;
System.out.println("1");
//use this array in this first function, and this function will modify it.
setArray_for_database(array_from_database);
myReff.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//this will be executed only if my array is changed (So only if my other function have been correctly executed
setAnotherArray(array_from_database);
System.out.println("3");
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Here's the code for setArray_for_database :
public void setArray_for_database(ArrayList<Integer> array_from_database){
FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myReff =database.getReference();
//Take the array from my database (gived in parameter)
final ArrayList<Integer> array = array_from_database;
myReff.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//this will be executed, even if data hasn't been changed because of the method (addListenerForSingleValueEvent)
System.out.println("2");
array.set(0,3);
Map<String, Object> chemainChild = new HashMap<>();
chemainChild.put("server/user/",array);
myReff.updateChildren(chemainChild);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
Here's my trick. The purpose of having an myReff.addValueEventListener(new ValueEventListener()inside another myReff.addListenerForSingleValueEvent(new ValueEventListener() is to only execute onDataChange if my array from database has been changed. But the problem is that it's not the case.
Here's what's print first : 1, 3, 2 instead of 1, 2, 3 like I'm expecting with my trick.
Can you help me ? Am I handling the problem in the wrong way ? How must I do to only execute my second function, in condition that my array has been changed ? How can I keep my code waiting for changes in my database before executing something else ?
Hope you have understood me and feel free to ask me question if you doesn't understand something in my problem.

Change this:
myReff.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//this will be executed only if my array is changed (So only if my other function have been correctly executed
setAnotherArray(array_from_database);
System.out.println("3");
}
to this:
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myReff = database.getReference();
myReff.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
DatabaseReference ref2 = database.getReference();
//Take the array from my database (gived in parameter)
final ArrayList<Integer> array = array_from_database;
System.out.println("2");
array.set(0,3);
Map<String, Object> chemainChild = new HashMap<>();
chemainChild.put("server/user/",array);
myReff.updateChildren(chemainChild);
System.out.println("3");
}
Some code may be missing from the above, but you get the idea add the code that is in the method public void setArray_for_database(ArrayList<Integer> array_from_database){ for this to work.
Since onDataChange is asynchronous meaning the program will keep executing, even if data is still not retrieved then this is the only way.
The reason you get 1-3-2, is because of this:
It entered the first onDataChange
It printed "1"
It entered the second onDataChange that is inside the first.
It printed "3" since it is asynchronous, then when the data was retrieved
It called the method and printed "2"
So the best thing is to add the data in the method inside the onDataChange
The above will fix the asynchronous problem that you had in the question. But you really should denormalize your data.
Read this to learn more: https://firebase.googleblog.com/2013/04/denormalizing-your-data-is-normal.html

Related

How to get the random key paired with a specific value in firebase database and delete the value?

I have a simple database structure like so:
I need to remove the value earphone by using its key which is -M-4XBDqv-ve396r5EHm.
I have a button for deleting that value,
and I want to use this line of code to remove it:
mReferenceForListA.child("List A").child("Items").child(key).removeValue().
But how do I get the key for earphone?
I know I can just write
mReferenceForListA.child("List A").child("Items").child(-M-4XBDqv-ve396r5EHm).removeValue()
to delete earphone straight away,
but what if I need to delete other items in the list?
I'll need to pass a specific key into a variable called "key" to delete a specific value.
So, again, how could I get a specific key for the value that I want to delete in my current JSON structure?
This is what I tried:
Reading through related posts for days in stackoverflow and I came down to writing the following codes, but I don't think I got the hang of this thing yet and seriously need help. The following codes always delete the whole "List A" node, but in this case I just need to delete one specified value and keep the others intact in the node.
public static DatabaseReference mRootReference = FirebaseDatabase.getInstance().getReference();
public static DatabaseReference mReferenceForListA = mRootReference.child("List A");
deleteButton= findViewById(R.id.delete_button)
deleteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mReferenceForListA.child("Items").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot postsnapshot :dataSnapshot.getChildren()) {
String key = postsnapshot.getKey();
mReferenceForListA.child("Items").child(key).removeValue();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
});
To delete a node you need to know its complete path. If you only know a value of (a property of) the node, you can run a query to find all nodes that match that value.
Something like:
public static DatabaseReference mRootReference = FirebaseDatabase.getInstance().getReference();
public static DatabaseReference mReferenceForListA = mRootReference.child("List A");
Query query = mReferenceForListA.child("Items").orderByValue().equalTo("earphone");
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot: dataSnapshot.getChildren()) {
snapshot.getRef().removeValue();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
throw databaseError.toException();
}
});
The main changes:
The above uses a query on orderByValue() on the Items child. This means Firebase searches all child nodes directly under List A/Items and returns the ones whose value matches earphones.
It also uses addListenerForSingleValueEvent, so that the delete only runs once, instead of continuously.
It uses snapshot.getRef().removeValue() for a much shorter way to remove the node.
It implements onCancelled, since you should never ignore errors.
You should iterate through the DataSnapshot as you have done, but you also need to supply it with a condition to delete the entry with a specific value.
for (DataSnapshot postsnapshot : dataSnapshot.getChildren()) {
if(postsnapshot.child(postsnapshot.getKey()).getValue(String.class)).equals("earphone"){
mReferenceForListA.child("List A").child("Items").child(postsnapshot.getkey()).removeValue();
}
}
Let me know if this works as I am trying to write this from memory.

Firebase ValueEventListener not triggered when attached to DatabaseReference

I have a Firebase Realtime Database. I can write to the database without any problems however I can not read from it. I read on the docs that adding a ValueEventListener will trigger when attached and every time the data changes in the db. However, my onDataChanged Method never fires.
My code is shown below:
patientReference = databaseReference.child("patients")
patientReference.addValueEventListener(profileListener);
private ValueEventListener profileListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
System.out.println("In onDataChange");
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("inInCancelled");
}
};
Neither of the println get executed. Not sure what is going wrong but any help would be appreciated.
Thanks!

Firebase - retrieving data by value to an outside variable

I have a question regarding Firebase Realtime database.
I'm trying to do a bookmark option in my program, which allows the user to store his/her's favourite pages, however whenever I try to retrieve data from my firebase database, the data is restored after the method returns a value.
public static boolean checkIfBookmarked(final String title){
final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
final DatabaseReference userBookmarks = FirebaseDatabase.getInstance().getReference().child("users")
.child(user.getUid()).child("bookmarks");
final boolean[] exists = new boolean[1];
userBookmarks.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
exists[0] = dataSnapshot.child(title).exists() ;
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return exists[0];
}
Firebase use callback methods to get the data from the server, In your case the return statement will be executed before the callback come from the Firbase. You can try to pass a callback method to your function and execute that when the callback from Firebase is triggered.
public static void checkIfBookmarked(final String title, callbackFunction){
final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
final DatabaseReference userBookmarks = FirebaseDatabase.getInstance().getReference().child("users")
.child(user.getUid()).child("bookmarks");
final boolean[] exists = new boolean[1];
userBookmarks.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
exists[0] = dataSnapshot.child(title).exists() ;
//execute your callback function here
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return;
}
Check this to see how to pass a function as parameter.
Passing function as a parameter in java
An alternative would be to move your code into OnDataChange method
You cannot return something now that hasn't been loaded yet. With other words, you cannot simply return the first element of your array exists[0], outside the onDataChange() method because it will always be null due the asynchronous behaviour of this method. This means that by the time you are trying to use that result outside that method, the data hasn't finished loading yet from the database and that's why is not accessible.
A quick solve for this problem would be to use exists[0] only inside the onDataChange() method, or if you want to use it outside, I recommend you dive into the asynchronous world and 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.

Retrieving child value -firebase-

System.out.println(ref.child("mostafa_farahat22#yahoo.com").child("_email"));
*i`m trying to get a value of child but all time i get the URL of the value
how to get the value of this URL as i try by this code but it get me the URLi want to get the _email value.
You are looking at the concept from the wrong angle.
While using the ref.child("mostafa_farahat22#yahoo.com").child("_email") you are just simply pointing at a particular place in your database and nothing more.
If you want to retrieve the data in that particular place, consider these 2 ways.
First if you want to retrieve the data only once, you can do the following :
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
DatabaseReference mostafa = ref.child("Users").child("mostafa_farahat22#yahoo.com").child("_email");
mostafa.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String email = dataSnapshot.getValue(String.class);
//do what you want with the email
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
or maybe you want to retrieve the value in real time and use it in the same time that the database value is changed, all in the same time, whenever the value in changed, then you use this :
mostafa.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String email = dataSnapshot.getValue(String.class);
display.setText(email);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Note the difference between the two methods. First is only for one time retrieve and the second is for retrieving the data whenever the value is changed.
Just have in mind that the codes that i posted are just templates and may need to play with them a bit.
With ref.child("mostafa_farahat22#yahoo.com").child("_email") you are just setting the reference to the object. Take a look at the java firebase documentation to retrieve data.
To get the data you will need to set a listener for your reference
ref.child("mostafa_farahat22#yahoo.com").child("_email").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
// data available in snapshot.value()
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});

Google firebase check if child exists

In my app, I need to check if a given element of my database on firebase has a child with a given name. I hoped it could be done by using something along the lines of:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
if (rootRef.childExists("name")) {
//run some code
}
I searched but I couldn't find anything useful.
Edit 2; worth putting on top: I think it is worth mentioning that this is actually downloading all data at this snapshot just to check whether any data exists. You should be mindful here. If the reference is huge (e.g. actually the root reference and not a specific child/property) then you should either find a deeper node you can use to check for existence or design your data structure differently so an efficient check is possible.
A database reference is effectively the URL for that data. You want to actually get data to see whether a child exists. This is why the method you seem to be looking for is on DataSnapshot.
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
void onDataChange(DataSnapshot snapshot) {
if (snapshot.hasChild("name")) {
// run some code
}
}
});
Now, this design pattern feels a bit strange. You're reading the whole database just to see whether "name" exists. You can make this a bit more efficient by listening to rootRef.child("name") and then just checking whether snapshot.exists().
If you're trying to do validation here, and not control flow, you should consider putting this code in your rules.json.
edit: I originally used the wrong function name (childExists instead of hasChild)
Don't do like this
NEVER
It will take all your data and bring to device
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
void onDataChange(DataSnapshot snapshot) {
if (snapshot.hasChild("name")) {
// run some code
}
}
});
Check it by this way.
It will return the value of the child if exists, otherwise -> null
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.child("childName")
rootRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
void onDataChange(DataSnapshot snapshot) {
if (snapshot.getValue() == null) {
// The child doesn't exist
}
}
});
A complete solution. No need to download all the data. Just check if the child exist like this:
// Assuming the names are in the name node. Remove "name/" if not
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference("name/" + name);
rootRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
// Exist! Do whatever.
} else {
// Don't exist! Do something.
}
}
#Override
public void onCancelled(DatabaseError error) {
// Failed, how to handle?
}
});
Try using .childexists in combination with .equalTo("Your specific name")
UsersRef = FirebaseDatabase.getInstance().getReference();
Users.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.hasChild("childName")) {
// it exists!
}else{
// does not exist
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
Use snapshot.exists() to check if the referenced database entry contains a child , irrespective of the value of the child.

Categories