Problems with Firestore connections executing threads - java

First of all, I would like to apologize if the title is misleading. English is not my native language and I wasn't sure how to name this post. Now the question:
I have an Activity that shows the data stored in a Firebase project about a user. The data is distributed between the Firebase user (display name, email and profile image) and a document in Cloud Firestore named as the user's UID.
When this activity starts, I make a Firebase google auth to get the user, and then the problems come. I need to know if the user has a linked document in the database with his additional data (existing user) or if he needs to create one (new user). I have created a method that checks if a document named like the user's UID exists. This is the method:
public void userExists(String uid) {
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docRef = db.collection("users").document(uid);
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot documentSnapshot = task.getResult();
if (documentSnapshot.exists()) {
aa = true;
aa=true;
} else {
aa = false;
}
} else {
aa = false;
}
}
});
}
(aa is a boolean variable declared in the Activity).
I call this method inside the following one in order to know if I need to start a new Activity to create the document or if I can show the user's data in the current Activity without problems.
private void updateUI(FirebaseUser user) {
if (user != null) {
userExists(user.getUid());
if(aa){
//Fill layout with the user data and the user linked document data
//USER DATA
txvNombre=findViewById(R.id.nombrePerfil);
txvNombre.setText(user.getDisplayName());
imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
Picasso.with(VistaPerfilActivity.this)
.load(user.getPhotoUrl())
.resize(500,500)
.centerCrop()
.into(imvAvatar);
//HERE GOES THE DOCUMENT DATA
}else{
}
} else {
finish();
}
}
As far as I know, Firestore connections are made in a new Thread so, when UpdateUI(FirebaseUser user) starts, aa is always false, because userExists(String uid) hasn't finished yet. userExists(String uid) works correctly, I have checked it.
So I need to know how to check if the Firestore connection thread is finished, in order to continue executing the app. I have tried using the OnCompleteListener (shown in the code), but it doesn't work. I've also tried to just write the actions in the userExists(String uid) method instead of just changing the value of aa and then continue on another method, but I get the
variable is accessed from within inner class needs to be declared final
error. I tried to follow the Android Studio advice of making the variable final, but I can't work with that for obvious reasons.
Thanks in advance.

The problem is not so much caused by multi-thread, as by the fact that data is loaded from Firebase asynchronously. By the time your updateUI function looks at the value of aa, the onComplete hasn't run yet.
This is easiest to see by placing a few well placed logging statements:
System.out.println("Before attaching listener");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
System.out.println("Got document");
}
});
System.out.println("After attaching listener");
When you run this code it prints
Before attaching listener
After attaching listener
Got document
This is probably not what you expected, but it explains precisely why aa is unmodified when updateUI checks it. The document hasn't been read from Firestore yet, so onComplete hasn't run yet.
The solution for this is to move all code that requires data from the database into the onComplete method. The simplest way in your case is:
public void userExists(String uid) {
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docRef = db.collection("users").document(uid);
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot documentSnapshot = task.getResult();
if (documentSnapshot.exists()) {
//Fill layout with the user data and the user linked document data
//USER DATA
txvNombre=findViewById(R.id.nombrePerfil);
txvNombre.setText(user.getDisplayName());
imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
Picasso.with(VistaPerfilActivity.this)
.load(user.getPhotoUrl())
.resize(500,500)
.centerCrop()
.into(imvAvatar);
//HERE GOES THE DOCUMENT DATA
}
}
}
});
}
Now your code that needs the document only runs after the document is actually available. This will work, but it does make the userExists function a bit less reusable. If you want to fix that, you can pass a callback into userExists that you then call after the document is loaded.
public interface UserExistsCallback {
void onCallback(boolean isExisting);
}
And use that in userExists as:
public void userExists(String uid, final UserExistsCallback callback) {
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docRef = db.collection("users").document(uid);
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
boolean userExists = false;
if (task.isSuccessful()) {
DocumentSnapshot documentSnapshot = task.getResult();
userExists = documentSnapshot.exists();
}
callback.onCallback(userExists);
}
});
}
And then invoke that from updateUI with:
if (user != null) {
userExists(user.getUid(), new UserExistsCallback() {
public void onCallback(boolean isExisting) {
if(isExisting){
//Fill layout with the user data and the user linked document data
//USER DATA
txvNombre=findViewById(R.id.nombrePerfil);
txvNombre.setText(user.getDisplayName());
imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
Picasso.with(VistaPerfilActivity.this)
.load(user.getPhotoUrl())
.resize(500,500)
.centerCrop()
.into(imvAvatar);
//HERE GOES THE DOCUMENT DATA
}else{
}
} else {
finish();
}
});
}
As you can see our UserExistsCallback is quite similar to the OnCompleteListener of Firestore itself, it's just a bit more tailored to our needs.
This problem pops up a lot, so I recommend spending some time learning more about it. See:
get all table values from firebase null object reference firebase database
Setting Singleton property value in Firebase Listener

Regarding the final reference, if the variable belongs to a class rather than being declared in the method, it need not be declared final.

You're right guessing that, when using the value of your aa variable outside the onComplete() method, the data hasn't finished loading yet from the database and that's why it's not accessible. So this variable will aways hold the initial value of false.
So in order to solve this, you need to wait for it. This can be done in two ways. The first solution would be a very quick solution that implies you to use the value of your aa variable only inside the onComplete() method and it will work perfectly fine. The second one is, if you want to use it outside the onComplete() method, I recommend you to 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.

Related

Callback in Firestore still returning null values from database

After my last question regarding reading data from a Firestore collection, I had to create a callback function in order to use the asynchronized data that Firestore sends. However, even with the callback function created, security rules in Firestore console overruled and a guarantee that there is a connection between my app and the database, I still get a null value.
Here is my callback interface
public interface Callback {
void myResponseCallback(String result);
}
And here is my updated code to work with this callback:
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docRef =
db.collection("usuarios")
.document("idtest");
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
tvMenu = findViewById(R.id.tvMainMenu);
btOpenOs = findViewById(R.id.btNewOs);
}
public void readDataFromFirestore(Callback callback){
//The app doesn't even enter this part of the code, as if they stop right at the first IF
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
dbValue = document.getString("palavra");
//tvMenu.setText(dbValue); DOESN'T WORK
Toast.makeText(MenuActivity.this, "String Value: " + document.getString("palavra"), Toast.LENGTH_SHORT).show();
}
}
callback.myResponseCallback(dbValue);
}
});
}
public void setDataFromFirestore() {
readDataFromFirestore(new Callback() {
#Override
public void myResponseCallback(String result) {
tvMenu.setText(result);
//Returns null
Toast.makeText(MenuActivity.this, "Result: " + result, Toast.LENGTH_SHORT).show();
}
});
}
public void openOsActivity(View view) {
/* Intent osIntent = new Intent(this, OsActivity.class);
startActivity(osIntent);*/
setDataFromFirestore();
}
The Toast from setDataFromFirestore() returns a "Result: null". Here's a picture of my FireStore collection of which I'm trying to read the field "palavra":
What exactly am I doing wrong here? It's my first time with Firestore and even after reading the documentation and other SO posts, I'm stuck. Any help would be greatly appreciated.
EDIT
Running the debugger, it seems that the result is also null. Here is a image from it:
However I could use some help understanding some of the information in there.
The problem as Frank van Puffelen described was that my code was loading "idtest" instead of idteste, as it is in my firestore database. Apart from that, it seems that the callback function made all of this possible.

Firebase Firestore get data from Document Java Android [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.

changes to ArrayList Dont persist after for loop , inside onDataChange of 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.

Query Firestore data and add one field to all the documents that match the query

I am building an Android app to sell books. My users can post ads for their used books and sell them. I am planning to add a feature where my users can opt to go anonymous. There will be checkbox with name Make me anonymous. If users check that box, their phone number and name will not be visible to others. Only a generic name should be visible.
Now the problem is, I want to put an entry anonymous = true in every ad documents that the user uploaded.
I want to query the ads that the user put and add a field anonymous = true. I want to do something like below:
final CheckBox anonymousCB = findViewById(R.id.anonymousCB);
anonymousCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (anonymousCB.isChecked()){
WriteBatch batch = firestore.batch();
DocumentReference sfRef = firestore.collection("books").whereEqualTo(uid,uid);
batch.update(sfRef, "anonymous", "true");
}
}
});
But I cannot make a query and insert a field into all the documents that match the query. Is there any better way to do this?
Is there any better way to do this?
Yes, there is. To solve this, please use the following lines of code inside onCheckedChanged() method:
Query sfRef = firestore.collection("books").whereEqualTo(uid, uid);
sfRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<String> list = new ArrayList<>();
for (DocumentSnapshot document : task.getResult()) {
list.add(document.getId());
}
for (String id : list) {
firestore.collection("books").document(id).update("anonymous", true).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "anonymous field successfully updated!");
}
});
}
}
}
});
The result of this code would be to add the anonymous property to all your book objects and set it to true. Please note, I have used the boolean true and not the String true as you have used in your code. Is more convenient to be used in this way.
P.S. If you are using a model class for your books, please also see my answer from this post.

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.

Categories