Custom Object Retrieved from Firebase Always Has Null Attributes - java

I'm trying to retreive a custom User object from Firebase as follows:
getUserFromDB(loggedInUserEmail);
viewModel = new ViewModelProvider(this).get(UserViewModel.class);
viewModel.sendUser(loggedInUser);
//...
public void getUserFromDB(String userEmail) {
DocumentReference docRef = db.collection("users").document(userEmail);
docRef.get().addOnSuccessListener(documentSnapshot -> {
loggedInUser = documentSnapshot.toObject(User.class);
Log.d("User Login", documentSnapshot.getId() + " => " + documentSnapshot.getData());
Toast.makeText(MainActivity.this, "Login successful.", Toast.LENGTH_SHORT).show();
});
}
However, the user being retreived always has null attributes.
Here's a screenshot from when I was debugging.
Note: I made sure my User class has a public empty constructor and all the attributes have a getter method.

Loading the user from the database is an asynchronous operation. While the data is being loaded, the main code continues and your viewModel.sendUser(loggedInUser) executes before the data is loaded. Then the success callback is fired and sets loggedInUser, but by that time the view model has already been initialized with the wrong value.
The rule is always quite simple: any code that needs data from the database, needs to be inside the success listener (or be called from there). So something like:
public void getUserFromDB(String userEmail) {
DocumentReference docRef = db.collection("users").document(userEmail);
docRef.get().addOnSuccessListener(documentSnapshot -> {
loggedInUser = documentSnapshot.toObject(User.class);
// 👈 Initialize ViewModelProvider here
viewModel = new ViewModelProvider(this).get(UserViewModel.class);
viewModel.sendUser(loggedInUser);
});
}
Also see:
How to check a certain data already exists in firestore or not

Related

How do i create a key with FirebaseDatabase

How can I program a key in my FirebaseDatabase that is created by the userName. wText should then be the text: "" Value.
I try to create a key but when i run my app it removes all values in my database
The Code:
`
// getting text from our edittext fields.
String nameValue = userName.getText().toString();
String textValue = wText.getText().toString();
// below line is for checking whether the
// edittext fields are empty or not.
if (nameValue.isEmpty() && textValue.isEmpty()) {
// if the text fields are empty
// then show the below message.
Toast.makeText(AddTextActivity.this, "Please add some data.", Toast.LENGTH_SHORT).show();
return;
} else {
// else call the method to add
// data to our database.
addDatatoFirebase(nameValue, textValue);
finish();
}
}
});
}
private void addDatatoFirebase(String name, String wText) {
// below 3 lines of code is used to set
// data in our object class.
Messages.setuserName(name);
Messages.setText(wText);
// we are use add value event listener method
// which is called with database reference.
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
// inside the method of on Data change we are setting
// our object class to our database reference.
// data base reference will sends data to firebase.
myRef.setValue(text);
// after adding this data we are showing toast message.
Toast.makeText(AddTextActivity.this, "data added", Toast.LENGTH_SHORT).show();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
// if the data is not added or it is cancelled then
// we are displaying a failure toast message.
Toast.makeText(AddTextActivity.this, "Fail to add data" + error, Toast.LENGTH_SHORT).show();
}
}); `
It seems you might be using set() function to update data in firebase.
( in your case setValue(), I'm not sure if it is Firebase function or user defined function , Firebase uses set() method in almost in every SDK, not sure about Java SDK for Firebase )
Please note that set() will replace the existing value, or create a new key if there isn't any.
Use update() function instead of set()
Ways to Save Data
set - Write or replace data to a defined path, like messages/users/
update - Update some of the keys for a defined path without replacing all of the data
push - Add to a list of data in the database. Every time you push a new node onto a list, your database generates a unique key, like
messages/users//
transaction - Use transactions when working with complex data that could be corrupted by concurrent updates
refer the docs here

Problems with Firestore connections executing threads

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.

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.

why the ArrayList in onDataChange method of addValueEventListener of firebase is not null, but outisde it is null?

In my project I read data from Firebase and added my data in Arraylist<object>.
I create my Arraylist in oncreate() and used it in onDataChange().
In onDataChange method of addValueEventListener the ArrayList is not null, it's read correctly. But outside it is read 0 (null). Why is that?
My code:
public class MainActivity extends AppCompatActivity {
private ArrayList<myObject> myListOfObjact;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Write a message to the database
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference();
myListOfObjacts= new ArrayList<myObject>();
Log.e(TAG, " myListOfObjacts.size: " + myListOfObjacts.size());
// size of myListOfObjacts here is 0 (null)
// Read from the database
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot postsnapshot : dataSnapshot.getChildren()) {
myObject data1 = postsnapshot.getValue(myObject.class);
myListOfObjacts.add(data1);
}
Log.e(TAG, " myListOfObjacts.size: " + myListOfObjacts.size());
// size of myListOfObjacts here is reading correctly
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w(TAG, "Failed to read value.", error.toException());
}
});
Log.e(TAG, " myListOfObjacts.size: " + myListOfObjacts.size());
// size of myListOfObjacts here also is 0 (null)
}
}
The Firebase Database client performs all network and disk operations off the main thread.
The Firebase Database client invokes all callbacks to your code on the main thread.
Therefore, onDataChange() is almost always triggered after other main (UI) thread based methods such as onCreate(), onStart()... because onDataChange() relies on your device's internet speed. Actually the null value that you see was null at the time it was first logged or invoked, but not null at the time you see the values populated on your layout.
addValueEventListener() is asynchronous, meaning it returns to the caller immediately, while the Firebase SDK goes and makes the request to the server while your code continues to execute. The callback you pass to it happens some time later, after the request is complete. You don't know how long that's going to be, and you shouldn't make any assumptions about that. Right now, your code is assuming that the results are available immediately after you add the listener, and that's not valid.
If you want the results of a listener, you should only do that from within the listener. You can't expect the results to be available before then.
If you want to know more about why Firebase APIs are asynchronous, read this blog.

Deleting a document from Firestore via Auto-generated document ID

I'm trying to create a Property Rental application on Android using Firebase Firestore. Right now, I'm trying to implement a method to delete a specific document (property) within my collection inside Firestore. I figure it is by referencing the auto-generated ID for that particular document, but I simply couldn't get around it.
This is how the delete feature should work:
User clicks on a property item from the RecyclerView
It displays a full profile of that property
User clicks the delete button from the top right corner and deletes the property from the Firestore database
Here is my code where I'm stucked at:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
// The delete button
case R.id.action_delete_btn:
// Do this when user clicks on delete button
Toast.makeText(PropertyProfile.this, "You tried to delete this property", Toast.LENGTH_LONG).show();
deleteItem(item.getOrder());
return super.onOptionsItemSelected(item);
default:
return false;
}
}
// Here's my problem
private void deleteItem(int index) {
firebaseFirestore.collection("Posts")
.document("[DOCUMENT ID RIGHT HERE!]")
.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(PropertyProfile.this, "You successfully deleted this property", Toast.LENGTH_LONG).show();
}
});
}
In order to use the document id that you are looking for, first you need to store it in a variable. When you are adding a document to the database and you are using a call to document() method without passing an argument, a unique id is generated. To get that id, you should use the following code:
String documentId = postsRef.document().getId();
postsRef.document(documentId).set(yourModelObject);
In which postsRef is the CollectionReference object of your Posts collection and yourModelObject is the object of your Post class. I also recommend you store that id, as a property of your Post document.
Once you have this id, you can use in your refence like this:
firebaseFirestore
.collection("Posts")
.document(documentId)
.delete().addOnSuccessListener(/* ... */);

Categories