Retrieving data from Realtime Database only once - android

I want my app to allow the user to back up his data on Realtime Database and restore it. I've already implemented the backup function but I got stuck when I tried to implement the restore function. Because a ChildEventListener only gives you the ability to retrieve data live, which is a problem because I need to know when the backup finished downloading. How can I retrieve data only once?

To retrieve the data only once, you need to use the following:
public void addListenerForSingleValueEvent (ValueEventListener listener)
Add a listener for a single change in the data at this location. This listener will be triggered once with the value of the data at the location.

You need to use addListenerForSingleValueEvent() instead
For Example :
FirebaseDatabase.getInstance()
.getReference()
.child("Your Node")
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
// Do whatever you need with your data (retrieved only once)
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
// Handle Database Error
}
});

For a database reference object, the same way one can add an event listener, it can also be removed, using removeEventListener.
Instead of creating an anonymous object like this
checkBuy.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
checkPurchase(snapshot);
addListView();
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
you can create a named object of ValueEventListener and remove it from the database reference object using removeEventListener, at the end of the onDataChange method
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
checkPurchase(snapshot);
addListView();
checkBuy.removeEventListener(valueEventListener);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
checkBuy.addValueEventListener(valueEventListener);
The code inside onDataChange method gets executed only once as the ValueEventListener object is removed as soon as the last line of the method gets executed.

Related

Writing from firebase realtime database to List [duplicate]

I don't have much experience with Java. I'm not sure if this question is stupid, but I need to get a user name from Firebase realtime database and return this name as a result of this method. So, I figured out how to get this value, but I don't understand how to return it as result of this method. What's the best way to do this?
private String getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
This is a classic issue with asynchronous web APIs. You cannot return something now that hasn't been loaded yet. In other words, you cannot simply create a global variable and use it outside onDataChange() method because it will always be null. This is happening because onDataChange() method is called asynchronous. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available.
But not only Firebase Realtime Database loads data asynchronously, but almost all modern web APIs also do, since it may take some time. So instead of waiting for the data (which can lead to unresponsive application dialogs for your users), your main application code continues while the data is loaded on a secondary thread. Then when the data is available, your onDataChange() method is called, and can use the data. In other words, by the time onDataChange() method is called your data is not loaded yet.
Let's take an example, by placing a few log statements in the code, to see more clearly what's going on.
private String getUserName(String uid) {
Log.d("TAG", "Before attaching the listener!");
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
Log.d("TAG", "Inside onDataChange() method!");
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
Log.d("TAG", "After attaching the listener!");
}
If we run this code will, the output will be:
Before attaching the listener!
After attaching the listener!
Inside onDataChange() method!
This is probably not what you expected, but it explains precisely why your data is null when returning it.
The initial response for most developers is to try and "fix" this asynchronous behavior, which I personally recommend against this. The web is asynchronous, and the sooner you accept that the sooner you can learn how to become productive with modern web APIs.
I've found it easiest to reframe problems for this asynchronous paradigm. Instead of saying "First get the data, then log it", I frame the problem as "Start to get data. When the data is loaded, log it". This means that any code that requires the data must be inside onDataChange() method or called from inside there, like this:
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
if(dataSnapshot != null) {
System.out.println(dataSnapshot.getValue(String.class));
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
If you want to use that outside, there is another approach. You need to create your own callback to wait for Firebase to return you the data. To achieve this, first, you need to create an interface like this:
public interface MyCallback {
void onCallback(String value);
}
Then you need to create a method that is actually getting the data from the database. This method should look like this:
public void readData(MyCallback myCallback) {
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
myCallback.onCallback(value);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
In the end just simply call readData() method and pass an instance of the MyCallback interface as an argument wherever you need it like this:
readData(new MyCallback() {
#Override
public void onCallback(String value) {
Log.d("TAG", value);
}
});
This is the only way in which you can use that value outside onDataChange() method. For more information, you can take also a look at this video.
Edit: Feb 26th, 2021
For more info, you can check the following article:
How to read data from Firebase Realtime Database using get()?
And the following video:
https://youtu.be/mOB40wowo6Y
Starting from the "19.6.0" version, the Firebase Realtime Database SDK contains a new method called get(), that can be called either on a DatabaseReference or a Query object:
Added DatabaseReference#get() and Query#get(), which return data from the server even when older data is available in the local cache.
As we already know, Firebase API is asynchronous. So we need to create a callback for that. First, let's create an interface:
public interface FirebaseCallback {
void onResponse(String name);
}
And a method that takes as an argument an object of tye FirebaseCallback:
public void readFirebaseName(FirebaseCallback callback) {
DatabaseReference uidRef = databaseReference.child(String.format("users/%s/name", uid))
uidRef.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
String name = task.getResult().getValue(String.class);
callback.onResponse(name);
} else {
Log.d(TAG, task.getException().getMessage());
}
}
});
}
Now, to read the data, you need to simply call the above method passing as an argument an object of type FirebaseCallback:
readFirebaseName(new FirebaseCallback() {
#Override
public void onResponse(String name) {
Log.d("TAG", name);
}
});
For more info, you can check the following article:
How to read data from Firebase Realtime Database using get()?
And the following video:
https://youtu.be/mOB40wowo6Y
I believe I understand what you are asking. Although you say you want to "return" it (per se) from the fetch method, it may suffice to say you actually just want to be able to use the value retrieved after your fetch has completed. If so, this is what you need to do:
Create a variable at the top of your class
Retrieve your value (which you have done mostly correctly)
Set the public variable in your class equal to value retrieved
Once your fetch succeeds, you could do many things with the variable. 4a and 4b are some simple examples:
4a. Edit:
As an example of use, you can trigger whatever else you need to run in your class that uses yourNameVariable (and you can be sure it yourNameVariable not null)
4b. Edit:
As an example of use, you can use the variable in a function that is triggered by a button's onClickListener.
Try this.
// 1. Create a variable at the top of your class
private String yourNameVariable;
// 2. Retrieve your value (which you have done mostly correctly)
private void getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// 3. Set the public variable in your class equal to value retrieved
yourNameVariable = dataSnapshot.getValue(String.class);
// 4a. EDIT: now that your fetch succeeded, you can trigger whatever else you need to run in your class that uses `yourNameVariable`, and you can be sure `yourNameVariable` is not null.
sayHiToMe();
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
// (part of step 4a)
public void sayHiToMe() {
Log.d(TAG, "hi there, " + yourNameVariable);
}
// 4b. use the variable in a function triggered by the onClickListener of a button.
public void helloButtonWasPressed() {
if (yourNameVariable != null) {
Log.d(TAG, "hi there, " + yourNameVariable);
}
}
Then, you can use yourNameVariable wherever you would like throughout your class.
Note: just be sure you check that yourNameVariable is not null when using it since onDataChange is asynchronous and may not have completed at the time you attempt to use it elsewhere.
Here's a crazy Idea, inside onDataChange, put it inside a TextView with visibility gone
textview.setVisiblity(Gone) or something,
then do something like
textview.setText(dataSnapshot.getValue(String.class))
then later get it with textview.getText().toString()
just a crazy simple Idea.
Use LiveData as return type and observe the changes of it's value to execute desired operation.
private MutableLiveData<String> userNameMutableLiveData = new MutableLiveData<>();
public MutableLiveData<String> getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
String userName = dataSnapshot.getValue(String.class);
userNameMutableLiveData.setValue(userName);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
return userNameMutableLiveData;
}
Then from your Activity/Fragment observe the LiveData and inside onChanged do your desired operation.
getUserName().observe(this, new Observer<String>() {
#Override
public void onChanged(String userName) {
//here, do whatever you want on `userName`
}
});
This is NOT a solution, just a way to access the data outside the method for code organization.
// Get Your Value
private void getValue() {
fbDbRefRoot.child("fbValue").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String yourValue = (String) dataSnapshot.getValue();
useValue(yourValue);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
// Use Your Value
private void useValue(String yourValue) {
Log.d(TAG, "countryNameCode: " + yourValue);
}
Another way of achieving result (but not necessarily a solution)
Declare a public variable
public static String aPublicVariable;
Set This Variable Inside The Async Method
aPublicVariable = (String) dataSnapshot.getValue();
Use The Variable Anywhere
Log.d(TAG, "Not Elegant: " + aPublicVariable);
In the second method if the async call is not long it will nearly work all the time.

When I am retrieving the data form Firebase the retrieving process is getting executed after some time [duplicate]

I don't have much experience with Java. I'm not sure if this question is stupid, but I need to get a user name from Firebase realtime database and return this name as a result of this method. So, I figured out how to get this value, but I don't understand how to return it as result of this method. What's the best way to do this?
private String getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
This is a classic issue with asynchronous web APIs. You cannot return something now that hasn't been loaded yet. In other words, you cannot simply create a global variable and use it outside onDataChange() method because it will always be null. This is happening because onDataChange() method is called asynchronous. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available.
But not only Firebase Realtime Database loads data asynchronously, but almost all modern web APIs also do, since it may take some time. So instead of waiting for the data (which can lead to unresponsive application dialogs for your users), your main application code continues while the data is loaded on a secondary thread. Then when the data is available, your onDataChange() method is called, and can use the data. In other words, by the time onDataChange() method is called your data is not loaded yet.
Let's take an example, by placing a few log statements in the code, to see more clearly what's going on.
private String getUserName(String uid) {
Log.d("TAG", "Before attaching the listener!");
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
Log.d("TAG", "Inside onDataChange() method!");
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
Log.d("TAG", "After attaching the listener!");
}
If we run this code will, the output will be:
Before attaching the listener!
After attaching the listener!
Inside onDataChange() method!
This is probably not what you expected, but it explains precisely why your data is null when returning it.
The initial response for most developers is to try and "fix" this asynchronous behavior, which I personally recommend against this. The web is asynchronous, and the sooner you accept that the sooner you can learn how to become productive with modern web APIs.
I've found it easiest to reframe problems for this asynchronous paradigm. Instead of saying "First get the data, then log it", I frame the problem as "Start to get data. When the data is loaded, log it". This means that any code that requires the data must be inside onDataChange() method or called from inside there, like this:
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
if(dataSnapshot != null) {
System.out.println(dataSnapshot.getValue(String.class));
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
If you want to use that outside, there is another approach. You need to create your own callback to wait for Firebase to return you the data. To achieve this, first, you need to create an interface like this:
public interface MyCallback {
void onCallback(String value);
}
Then you need to create a method that is actually getting the data from the database. This method should look like this:
public void readData(MyCallback myCallback) {
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
myCallback.onCallback(value);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
In the end just simply call readData() method and pass an instance of the MyCallback interface as an argument wherever you need it like this:
readData(new MyCallback() {
#Override
public void onCallback(String value) {
Log.d("TAG", value);
}
});
This is the only way in which you can use that value outside onDataChange() method. For more information, you can take also a look at this video.
Edit: Feb 26th, 2021
For more info, you can check the following article:
How to read data from Firebase Realtime Database using get()?
And the following video:
https://youtu.be/mOB40wowo6Y
Starting from the "19.6.0" version, the Firebase Realtime Database SDK contains a new method called get(), that can be called either on a DatabaseReference or a Query object:
Added DatabaseReference#get() and Query#get(), which return data from the server even when older data is available in the local cache.
As we already know, Firebase API is asynchronous. So we need to create a callback for that. First, let's create an interface:
public interface FirebaseCallback {
void onResponse(String name);
}
And a method that takes as an argument an object of tye FirebaseCallback:
public void readFirebaseName(FirebaseCallback callback) {
DatabaseReference uidRef = databaseReference.child(String.format("users/%s/name", uid))
uidRef.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
String name = task.getResult().getValue(String.class);
callback.onResponse(name);
} else {
Log.d(TAG, task.getException().getMessage());
}
}
});
}
Now, to read the data, you need to simply call the above method passing as an argument an object of type FirebaseCallback:
readFirebaseName(new FirebaseCallback() {
#Override
public void onResponse(String name) {
Log.d("TAG", name);
}
});
For more info, you can check the following article:
How to read data from Firebase Realtime Database using get()?
And the following video:
https://youtu.be/mOB40wowo6Y
I believe I understand what you are asking. Although you say you want to "return" it (per se) from the fetch method, it may suffice to say you actually just want to be able to use the value retrieved after your fetch has completed. If so, this is what you need to do:
Create a variable at the top of your class
Retrieve your value (which you have done mostly correctly)
Set the public variable in your class equal to value retrieved
Once your fetch succeeds, you could do many things with the variable. 4a and 4b are some simple examples:
4a. Edit:
As an example of use, you can trigger whatever else you need to run in your class that uses yourNameVariable (and you can be sure it yourNameVariable not null)
4b. Edit:
As an example of use, you can use the variable in a function that is triggered by a button's onClickListener.
Try this.
// 1. Create a variable at the top of your class
private String yourNameVariable;
// 2. Retrieve your value (which you have done mostly correctly)
private void getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// 3. Set the public variable in your class equal to value retrieved
yourNameVariable = dataSnapshot.getValue(String.class);
// 4a. EDIT: now that your fetch succeeded, you can trigger whatever else you need to run in your class that uses `yourNameVariable`, and you can be sure `yourNameVariable` is not null.
sayHiToMe();
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
// (part of step 4a)
public void sayHiToMe() {
Log.d(TAG, "hi there, " + yourNameVariable);
}
// 4b. use the variable in a function triggered by the onClickListener of a button.
public void helloButtonWasPressed() {
if (yourNameVariable != null) {
Log.d(TAG, "hi there, " + yourNameVariable);
}
}
Then, you can use yourNameVariable wherever you would like throughout your class.
Note: just be sure you check that yourNameVariable is not null when using it since onDataChange is asynchronous and may not have completed at the time you attempt to use it elsewhere.
Here's a crazy Idea, inside onDataChange, put it inside a TextView with visibility gone
textview.setVisiblity(Gone) or something,
then do something like
textview.setText(dataSnapshot.getValue(String.class))
then later get it with textview.getText().toString()
just a crazy simple Idea.
Use LiveData as return type and observe the changes of it's value to execute desired operation.
private MutableLiveData<String> userNameMutableLiveData = new MutableLiveData<>();
public MutableLiveData<String> getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
String userName = dataSnapshot.getValue(String.class);
userNameMutableLiveData.setValue(userName);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
return userNameMutableLiveData;
}
Then from your Activity/Fragment observe the LiveData and inside onChanged do your desired operation.
getUserName().observe(this, new Observer<String>() {
#Override
public void onChanged(String userName) {
//here, do whatever you want on `userName`
}
});
This is NOT a solution, just a way to access the data outside the method for code organization.
// Get Your Value
private void getValue() {
fbDbRefRoot.child("fbValue").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String yourValue = (String) dataSnapshot.getValue();
useValue(yourValue);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
// Use Your Value
private void useValue(String yourValue) {
Log.d(TAG, "countryNameCode: " + yourValue);
}
Another way of achieving result (but not necessarily a solution)
Declare a public variable
public static String aPublicVariable;
Set This Variable Inside The Async Method
aPublicVariable = (String) dataSnapshot.getValue();
Use The Variable Anywhere
Log.d(TAG, "Not Elegant: " + aPublicVariable);
In the second method if the async call is not long it will nearly work all the time.

My app crashes when I call a function that retrieves data from Firebase inside an if statement [duplicate]

I don't have much experience with Java. I'm not sure if this question is stupid, but I need to get a user name from Firebase realtime database and return this name as a result of this method. So, I figured out how to get this value, but I don't understand how to return it as result of this method. What's the best way to do this?
private String getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
This is a classic issue with asynchronous web APIs. You cannot return something now that hasn't been loaded yet. In other words, you cannot simply create a global variable and use it outside onDataChange() method because it will always be null. This is happening because onDataChange() method is called asynchronous. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available.
But not only Firebase Realtime Database loads data asynchronously, but almost all modern web APIs also do, since it may take some time. So instead of waiting for the data (which can lead to unresponsive application dialogs for your users), your main application code continues while the data is loaded on a secondary thread. Then when the data is available, your onDataChange() method is called, and can use the data. In other words, by the time onDataChange() method is called your data is not loaded yet.
Let's take an example, by placing a few log statements in the code, to see more clearly what's going on.
private String getUserName(String uid) {
Log.d("TAG", "Before attaching the listener!");
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
Log.d("TAG", "Inside onDataChange() method!");
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
Log.d("TAG", "After attaching the listener!");
}
If we run this code will, the output will be:
Before attaching the listener!
After attaching the listener!
Inside onDataChange() method!
This is probably not what you expected, but it explains precisely why your data is null when returning it.
The initial response for most developers is to try and "fix" this asynchronous behavior, which I personally recommend against this. The web is asynchronous, and the sooner you accept that the sooner you can learn how to become productive with modern web APIs.
I've found it easiest to reframe problems for this asynchronous paradigm. Instead of saying "First get the data, then log it", I frame the problem as "Start to get data. When the data is loaded, log it". This means that any code that requires the data must be inside onDataChange() method or called from inside there, like this:
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
if(dataSnapshot != null) {
System.out.println(dataSnapshot.getValue(String.class));
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
If you want to use that outside, there is another approach. You need to create your own callback to wait for Firebase to return you the data. To achieve this, first, you need to create an interface like this:
public interface MyCallback {
void onCallback(String value);
}
Then you need to create a method that is actually getting the data from the database. This method should look like this:
public void readData(MyCallback myCallback) {
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
myCallback.onCallback(value);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
In the end just simply call readData() method and pass an instance of the MyCallback interface as an argument wherever you need it like this:
readData(new MyCallback() {
#Override
public void onCallback(String value) {
Log.d("TAG", value);
}
});
This is the only way in which you can use that value outside onDataChange() method. For more information, you can take also a look at this video.
Edit: Feb 26th, 2021
For more info, you can check the following article:
How to read data from Firebase Realtime Database using get()?
And the following video:
https://youtu.be/mOB40wowo6Y
Starting from the "19.6.0" version, the Firebase Realtime Database SDK contains a new method called get(), that can be called either on a DatabaseReference or a Query object:
Added DatabaseReference#get() and Query#get(), which return data from the server even when older data is available in the local cache.
As we already know, Firebase API is asynchronous. So we need to create a callback for that. First, let's create an interface:
public interface FirebaseCallback {
void onResponse(String name);
}
And a method that takes as an argument an object of tye FirebaseCallback:
public void readFirebaseName(FirebaseCallback callback) {
DatabaseReference uidRef = databaseReference.child(String.format("users/%s/name", uid))
uidRef.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
String name = task.getResult().getValue(String.class);
callback.onResponse(name);
} else {
Log.d(TAG, task.getException().getMessage());
}
}
});
}
Now, to read the data, you need to simply call the above method passing as an argument an object of type FirebaseCallback:
readFirebaseName(new FirebaseCallback() {
#Override
public void onResponse(String name) {
Log.d("TAG", name);
}
});
For more info, you can check the following article:
How to read data from Firebase Realtime Database using get()?
And the following video:
https://youtu.be/mOB40wowo6Y
I believe I understand what you are asking. Although you say you want to "return" it (per se) from the fetch method, it may suffice to say you actually just want to be able to use the value retrieved after your fetch has completed. If so, this is what you need to do:
Create a variable at the top of your class
Retrieve your value (which you have done mostly correctly)
Set the public variable in your class equal to value retrieved
Once your fetch succeeds, you could do many things with the variable. 4a and 4b are some simple examples:
4a. Edit:
As an example of use, you can trigger whatever else you need to run in your class that uses yourNameVariable (and you can be sure it yourNameVariable not null)
4b. Edit:
As an example of use, you can use the variable in a function that is triggered by a button's onClickListener.
Try this.
// 1. Create a variable at the top of your class
private String yourNameVariable;
// 2. Retrieve your value (which you have done mostly correctly)
private void getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// 3. Set the public variable in your class equal to value retrieved
yourNameVariable = dataSnapshot.getValue(String.class);
// 4a. EDIT: now that your fetch succeeded, you can trigger whatever else you need to run in your class that uses `yourNameVariable`, and you can be sure `yourNameVariable` is not null.
sayHiToMe();
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
// (part of step 4a)
public void sayHiToMe() {
Log.d(TAG, "hi there, " + yourNameVariable);
}
// 4b. use the variable in a function triggered by the onClickListener of a button.
public void helloButtonWasPressed() {
if (yourNameVariable != null) {
Log.d(TAG, "hi there, " + yourNameVariable);
}
}
Then, you can use yourNameVariable wherever you would like throughout your class.
Note: just be sure you check that yourNameVariable is not null when using it since onDataChange is asynchronous and may not have completed at the time you attempt to use it elsewhere.
Here's a crazy Idea, inside onDataChange, put it inside a TextView with visibility gone
textview.setVisiblity(Gone) or something,
then do something like
textview.setText(dataSnapshot.getValue(String.class))
then later get it with textview.getText().toString()
just a crazy simple Idea.
Use LiveData as return type and observe the changes of it's value to execute desired operation.
private MutableLiveData<String> userNameMutableLiveData = new MutableLiveData<>();
public MutableLiveData<String> getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
String userName = dataSnapshot.getValue(String.class);
userNameMutableLiveData.setValue(userName);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
return userNameMutableLiveData;
}
Then from your Activity/Fragment observe the LiveData and inside onChanged do your desired operation.
getUserName().observe(this, new Observer<String>() {
#Override
public void onChanged(String userName) {
//here, do whatever you want on `userName`
}
});
This is NOT a solution, just a way to access the data outside the method for code organization.
// Get Your Value
private void getValue() {
fbDbRefRoot.child("fbValue").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String yourValue = (String) dataSnapshot.getValue();
useValue(yourValue);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
// Use Your Value
private void useValue(String yourValue) {
Log.d(TAG, "countryNameCode: " + yourValue);
}
Another way of achieving result (but not necessarily a solution)
Declare a public variable
public static String aPublicVariable;
Set This Variable Inside The Async Method
aPublicVariable = (String) dataSnapshot.getValue();
Use The Variable Anywhere
Log.d(TAG, "Not Elegant: " + aPublicVariable);
In the second method if the async call is not long it will nearly work all the time.

How to return an arrayList from onDataChange in android firebase realtime database [duplicate]

I don't have much experience with Java. I'm not sure if this question is stupid, but I need to get a user name from Firebase realtime database and return this name as a result of this method. So, I figured out how to get this value, but I don't understand how to return it as result of this method. What's the best way to do this?
private String getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
This is a classic issue with asynchronous web APIs. You cannot return something now that hasn't been loaded yet. In other words, you cannot simply create a global variable and use it outside onDataChange() method because it will always be null. This is happening because onDataChange() method is called asynchronous. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available.
But not only Firebase Realtime Database loads data asynchronously, but almost all modern web APIs also do, since it may take some time. So instead of waiting for the data (which can lead to unresponsive application dialogs for your users), your main application code continues while the data is loaded on a secondary thread. Then when the data is available, your onDataChange() method is called, and can use the data. In other words, by the time onDataChange() method is called your data is not loaded yet.
Let's take an example, by placing a few log statements in the code, to see more clearly what's going on.
private String getUserName(String uid) {
Log.d("TAG", "Before attaching the listener!");
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
Log.d("TAG", "Inside onDataChange() method!");
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
Log.d("TAG", "After attaching the listener!");
}
If we run this code will, the output will be:
Before attaching the listener!
After attaching the listener!
Inside onDataChange() method!
This is probably not what you expected, but it explains precisely why your data is null when returning it.
The initial response for most developers is to try and "fix" this asynchronous behavior, which I personally recommend against this. The web is asynchronous, and the sooner you accept that the sooner you can learn how to become productive with modern web APIs.
I've found it easiest to reframe problems for this asynchronous paradigm. Instead of saying "First get the data, then log it", I frame the problem as "Start to get data. When the data is loaded, log it". This means that any code that requires the data must be inside onDataChange() method or called from inside there, like this:
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
if(dataSnapshot != null) {
System.out.println(dataSnapshot.getValue(String.class));
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
If you want to use that outside, there is another approach. You need to create your own callback to wait for Firebase to return you the data. To achieve this, first, you need to create an interface like this:
public interface MyCallback {
void onCallback(String value);
}
Then you need to create a method that is actually getting the data from the database. This method should look like this:
public void readData(MyCallback myCallback) {
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
myCallback.onCallback(value);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
In the end just simply call readData() method and pass an instance of the MyCallback interface as an argument wherever you need it like this:
readData(new MyCallback() {
#Override
public void onCallback(String value) {
Log.d("TAG", value);
}
});
This is the only way in which you can use that value outside onDataChange() method. For more information, you can take also a look at this video.
Edit: Feb 26th, 2021
For more info, you can check the following article:
How to read data from Firebase Realtime Database using get()?
And the following video:
https://youtu.be/mOB40wowo6Y
Starting from the "19.6.0" version, the Firebase Realtime Database SDK contains a new method called get(), that can be called either on a DatabaseReference or a Query object:
Added DatabaseReference#get() and Query#get(), which return data from the server even when older data is available in the local cache.
As we already know, Firebase API is asynchronous. So we need to create a callback for that. First, let's create an interface:
public interface FirebaseCallback {
void onResponse(String name);
}
And a method that takes as an argument an object of tye FirebaseCallback:
public void readFirebaseName(FirebaseCallback callback) {
DatabaseReference uidRef = databaseReference.child(String.format("users/%s/name", uid))
uidRef.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
String name = task.getResult().getValue(String.class);
callback.onResponse(name);
} else {
Log.d(TAG, task.getException().getMessage());
}
}
});
}
Now, to read the data, you need to simply call the above method passing as an argument an object of type FirebaseCallback:
readFirebaseName(new FirebaseCallback() {
#Override
public void onResponse(String name) {
Log.d("TAG", name);
}
});
For more info, you can check the following article:
How to read data from Firebase Realtime Database using get()?
And the following video:
https://youtu.be/mOB40wowo6Y
I believe I understand what you are asking. Although you say you want to "return" it (per se) from the fetch method, it may suffice to say you actually just want to be able to use the value retrieved after your fetch has completed. If so, this is what you need to do:
Create a variable at the top of your class
Retrieve your value (which you have done mostly correctly)
Set the public variable in your class equal to value retrieved
Once your fetch succeeds, you could do many things with the variable. 4a and 4b are some simple examples:
4a. Edit:
As an example of use, you can trigger whatever else you need to run in your class that uses yourNameVariable (and you can be sure it yourNameVariable not null)
4b. Edit:
As an example of use, you can use the variable in a function that is triggered by a button's onClickListener.
Try this.
// 1. Create a variable at the top of your class
private String yourNameVariable;
// 2. Retrieve your value (which you have done mostly correctly)
private void getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// 3. Set the public variable in your class equal to value retrieved
yourNameVariable = dataSnapshot.getValue(String.class);
// 4a. EDIT: now that your fetch succeeded, you can trigger whatever else you need to run in your class that uses `yourNameVariable`, and you can be sure `yourNameVariable` is not null.
sayHiToMe();
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
// (part of step 4a)
public void sayHiToMe() {
Log.d(TAG, "hi there, " + yourNameVariable);
}
// 4b. use the variable in a function triggered by the onClickListener of a button.
public void helloButtonWasPressed() {
if (yourNameVariable != null) {
Log.d(TAG, "hi there, " + yourNameVariable);
}
}
Then, you can use yourNameVariable wherever you would like throughout your class.
Note: just be sure you check that yourNameVariable is not null when using it since onDataChange is asynchronous and may not have completed at the time you attempt to use it elsewhere.
Here's a crazy Idea, inside onDataChange, put it inside a TextView with visibility gone
textview.setVisiblity(Gone) or something,
then do something like
textview.setText(dataSnapshot.getValue(String.class))
then later get it with textview.getText().toString()
just a crazy simple Idea.
Use LiveData as return type and observe the changes of it's value to execute desired operation.
private MutableLiveData<String> userNameMutableLiveData = new MutableLiveData<>();
public MutableLiveData<String> getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
String userName = dataSnapshot.getValue(String.class);
userNameMutableLiveData.setValue(userName);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
return userNameMutableLiveData;
}
Then from your Activity/Fragment observe the LiveData and inside onChanged do your desired operation.
getUserName().observe(this, new Observer<String>() {
#Override
public void onChanged(String userName) {
//here, do whatever you want on `userName`
}
});
This is NOT a solution, just a way to access the data outside the method for code organization.
// Get Your Value
private void getValue() {
fbDbRefRoot.child("fbValue").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String yourValue = (String) dataSnapshot.getValue();
useValue(yourValue);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
// Use Your Value
private void useValue(String yourValue) {
Log.d(TAG, "countryNameCode: " + yourValue);
}
Another way of achieving result (but not necessarily a solution)
Declare a public variable
public static String aPublicVariable;
Set This Variable Inside The Async Method
aPublicVariable = (String) dataSnapshot.getValue();
Use The Variable Anywhere
Log.d(TAG, "Not Elegant: " + aPublicVariable);
In the second method if the async call is not long it will nearly work all the time.

How to get a value once of a firebase database (Android)

I just started using firebase and I'm amazed of how simple are complex things, and how complex simple things can be.
I need to check if a user exists in my Firebase database, and if exists, I need to get its key and value.
I have the key and I try to find it in the DB by going to my user subtree and loking for a child with the same key (ID)
DatabaseReference root_firebase = FirebaseDatabase.getInstance().getReference();
DatabaseReference users_firebase = root_firebase.child("Users");
DatabaseReference friend_found = users_firebase.child(key_searched_friend);
once I have it I try to call some method like
friend_found.getValue();
or
friend_found.child("user_name").getValue();
But it does not exist,
this seems weird since I can do both
friend_found.getKey();
friend_found.child("user_name").getKey();
I can't do this with the overriden method onDataChanged() since the data does not change when I query for this value.
This is my firebase database structure:
{
"RoomNames" : {
"Room-KM5cof0jcoMN8a4g6vC" : {
"room_name" : "TESTrOOM"
},
"Room-KM5dg_WPRdEOT4_oJ1r" : {
"room_name" : "testRoom2"
}
},
"Users" : {
"User-KM5ZaGq0xvjQis05CPF" : {
"user_name" : "Enrique"
}
}
}
You say: I can't do this with the overriden method onDataChanged() since the data does not change when I query for this value
The guide for how to Retrieve Data explains that:
Firebase data is retrieved by attaching an asynchronous listener to a
FirebaseDatabase reference. The listener is triggered once for the
initial state of the data and again anytime the data changes.
So when you attach a listener to a location, onDataChanged() fires and gives you the current value.
In the section titled Read Data Once, the guide states:
In some cases you may want a callback to be called once and then
immediately removed, such as when initializing a UI element that you
don't expect to change. You can use the
addListenerForSingleValueEvent() method to simplify this scenario: it
triggers once and then does not trigger again.
Use as below :
friend_found.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.e(TAG,"Error while reading data");
}
});
For a database reference object, the same way one can add an event listener, it can also be removed, using removeEventListener.
Instead of creating an anonymous object like this
friend_found.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.e(TAG,"Error while reading data");
}
});
you can create a named object of ValueEventListener and remove it from the database reference object using removeEventListener, at the end of the onDataChange method
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
friend_found.removeEventListener(valueEventListener);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.e(TAG,"Error while reading data");
}
});
friend_found.addValueEventListener(valueEventListener);
The code inside onDataChange method gets executed only once as the ValueEventListener object is removed as soon as the last line of the method gets executed.

Categories

Resources