Firebase reading issue. Object always null - java

I have a method which should initialize my field variable currentTask. I don't understand why I can't read my object from firebase. Here is the method:
private void getCurrentTask() {
final DatabaseReference dRef1 = database.getReference().child("Users").child(uid).child("CurrentTask");
dRef1.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
currentTask = dataSnapshot.getValue(CurrentTask.class);
} else {
Toast toast = Toast.makeText(TasksListActivity.this, "no magic", Toast.LENGTH_LONG);
toast.show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
if (currentTask == null) {
Toast toast = Toast.makeText(this, "magic)", Toast.LENGTH_LONG);
toast.show();
}
}
Toast no magic never appears, so the object exists. But, in the end of the method toast magic appears, which means currentTask == null. Even after initialization!
And here is my database:

#Roasario and #ReazMurshed 's answer is both right. But let me make it more simple by describing how your code doesn't work as you expected:
private void getCurrentTask() {
...
dRef1.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
... (point 1)
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
... (point 2)
}
Firebase Database load your data asyncronously. It means (to make it simple) that the process of getting data does not interfere with your main process. With that in mind, the code in (point 2) does not always get executed after code in (point 1), and vice versa. Though usually code in (point 1) get executed after (point 2), it is not always like that. So you should consider that the code you write in (point 1) can be executed at anytime.
Then you should write your code with that concept. Meaning that if you want to do anything to a variable inside (point 1) (like filling currentTask with dataSnapshot value then check if it is null), you should place it all inside (point 1)
Hope this help.

Note that onDataChange is asynchronous, so your if statement will always return false because you're checking if it is null while the data hasn't been read before. You should check if it is null inside of onDataChange (to assure the data has been read) like this:
private void getCurrentTask() {
final DatabaseReference dRef1 = database.getReference().child("Users").child(uid).child("CurrentTask");
dRef1.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
currentTask = dataSnapshot.getValue(CurrentTask.class);
if (currentTask == null) {
Toast toast = Toast.makeText(this, "magic)", Toast.LENGTH_LONG);
toast.show();
}
} else {
Toast toast = Toast.makeText(TasksListActivity.this, "no magic", Toast.LENGTH_LONG);
toast.show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}

Modify your reference url a bit like this.
final DatabaseReference dRef1 = database.getReference().child("Users").child(uid);
Now create a class to represent each of your nodes.
public class User {
public UserCharacter Character;
public UserCurrentTask CurrentTask;
public String Email;
public UserTasks Tasks;
}
Now inside your onDataChange you need to do this.
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
User mUser = dataSnapshot.getValue(User.class);
currentTask = mUser.CurrentTask;
} else {
// Show toast or something.
}
}
And as #Roasario stated, the onDataChanged function is Async. So you can't get the actual value while you check for null value.

Related

Get a specific nested child and count it

I making an Attendance App.
In the app there is a function to know how many event an users already attend and what event they attend.
What i want is something like this (i dont know if it work or there is a better way):
private void fetchSpecificEvent() {
//Fetch event that user already attend
FirebaseRecyclerOptions<ModelEvent> options = new FirebaseRecyclerOptions.Builder<ModelEvent>()
.setQuery(referenceForSpecificEvent, snapshot -> new ModelEvent(
Objects.requireNonNull(snapshot.child("eventID").getValue()).toString(),
...
...
...
)
.build();
...
}
private void getAttendanceCount(){
//Get how many event user already attend
referenceForCount.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
attendanceCount = (int) snapshot.getChildrenCount();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
The main problem is i dont know what reference i use on: referenceForSpecificEvent and referenceForCount
This the JSON:
This the full JSON : https://pastebin.com/k65jhMUC
Sorry for the title, i dont know the keyword for it.
I found the way to do it. Probably not the best (because i cant really explain what i want) but it works.
reference = FirebaseDatabase.getInstance().getReference().child("Attendance");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()){
for (DataSnapshot eventSnapshot: dataSnapshot.getChildren()) {
//Get Event ID
Log.i(TAG, eventSnapshot.getKey());
for (DataSnapshot userSnapshot: dataSnapshot.child(Objects.requireNonNull(eventSnapshot.getKey())).getChildren()) {
//Get User ID
//Log.i(TAG, userSnapshot.getKey());
if(Objects.equals(userSnapshot.getKey(), stringEmail)){
intCount++;
listEvent.add(eventSnapshot.getKey());
}
}
}
}
else{
Log.i(TAG, "No Value Found");
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.d(TAG, error.getMessage()); //Don't ignore errors!
}
});

Firebase Datasnapshot returns null value

I want to retrieve this value from this node ("id"), and the value i get is null. I have googled so many solutions that this might have to do with asynchronous way or something, i guess?
This is the database, and the highlighted node is the value i would like to get:
This is my code:
reference = FirebaseDatabase.getInstance().getReference();
id = null;
Query lastQuery = reference.child("Donation Request").orderByKey().limitToLast(1);
lastQuery.addListenerForSingleValueEvent(new ValueEventListener()
{
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot)
{
if (dataSnapshot.child("id").exists())
{
id = dataSnapshot.child("id").getValue().toString();
int index = Integer.parseInt(id) + 1;
id = Integer.toString(index);
Toast.makeText(getApplicationContext(), "It works!!!", Toast.LENGTH_SHORT).show();
}
else
{
id = "1";
Toast.makeText(getApplicationContext(), "It doesn't work.", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError)
{
}
});
Most appreciated if someone can help me out of this!
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
Your onDataChange needs to handle this list by looping over dataSnapshot.getChildren()):
reference = FirebaseDatabase.getInstance().getReference();
id = null;
Query lastQuery = reference.child("Donation Request").orderByKey().limitToLast(1);
lastQuery.addListenerForSingleValueEvent(new ValueEventListener()
{
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot)
{
for (DataSnapshot snapshot: dataSnapshot.getChildren()) {
if (snapshot.hasChild("id"))
{
id = snapshot.child("id").getValue(String.class);
int index = Integer.parseInt(id) + 1;
id = Integer.toString(index);
Toast.makeText(getApplicationContext(), "It works!!!", Toast.LENGTH_SHORT).show();
}
else
{
id = "1";
Toast.makeText(getApplicationContext(), "It doesn't work.", Toast.LENGTH_SHORT).show();
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError)
{
throw databaseError.toException(); // never ignore errors.
}
});
A few more notes:
Any use of id needs to happen inside onDataChange, or be called from there. Outside of that, you won't have any guarantees that id will have been assigned the value you expect.
Using toasts for debugging is bound to become confusing. I highly recommend using Log.d(...) and friends, and studying the output (and its order) in the logcat output of your app.

Detect value change with firebase ValueEventListener

I am trying to detect value change from my Firebase Database. Here is my code for initializing the ValueEventListener:
valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
try {
String customerLocation = String.valueOf(dataSnapshot.getValue());
Point customerPoint = locationFounder.getPoint(customerLocation);
if (customerPoint != null) {
databaseReference.child("isActive").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
boolean isActive = Boolean.valueOf(String.valueOf(dataSnapshot.getValue()));
displayPopUp(isActive, customerPoint, customerLocation);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
} catch (Exception e) {
Log.d("Listener",e.toString());
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
destinationReference.addValueEventListener(valueEventListener);
Problem occurs when I want to call this listener in my activity. I've been trying with this:
destinationListener.getValueEventListener().onDataChange(REQUIRED_SNAPSHOT);
I do not know how can I get datasnapshot which is required for onDataChange. I would like to get this work with ValueEventListener, not ChildEventListener, if possible. However, I am not pretty sure that this is the right way of trying to detect value change. If there is any other way that will work properly, I'd like to know about it.
There is nothing built to the Firebase Realtime Database to tell you what specific data under the snapshot has changed in the onDataChange method.
If you want to know what specific property has change, you'll need to:
Keep the snapshot that you get in onDataChange in a field.
When onDataChange gets called, compare the data in the new snapshot with the data in the field.
Say that you have a reference on a node in your JSON, and under that node is a status property, and you want to both listen to the entire node, and detect if the status has changed.
You'd do that with something like:
// Add a field to your class to keep the latest snapshot
DataSnapshot previousSnapshot;
// Then add your listener
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
bool wasActive = false;
if (previousSnapshot != null && previousSnapshot.child("status").exists()) {
wasActive = dataSnapshot.child("status").getValue(Boolean.class);
}
boolean isActive = dataSnapshot.child("status").getValue(Boolean.class);
if (isActive <> wasActive) {
... the user's status changed
}
previousSnapshot = dataSnapshot;
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
throw databaseError.toException(); // never ignore errors
}
});

Getting list of items/children from Firebase Database?

I'm running into some trouble figuring out how to get the number of items/children in my Firebase real-time database for my app to use as an int value.
Check the image below:
So as you can see above, I have 2 children of images_list.
What I want to do is: get the number of items/children of images_list returned to my app, which would obviously equal 2 initially, and have this number expand whenever I add more items/children.
So my question is, what code do I implement to grab the number of children in this database? And how could I then convert this number into an int value? I've already experimented with differing methods, but I haven't found a solution as of yet.
I hope that made sense. Thanks so much for any of your assistance!
Code solution down here; I had to move most of this code from my RecyclerView Adapter into my MainActivity for it to work
Interface
public interface FirebaseCallback {
void onCallback(List<String> list);
}
readData method
private void readData(final FirebaseCallback firebaseCallback) {
mDatabaseImagesRef = FirebaseDatabase.getInstance().getReference("images_list");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
String imageItems = ds.getValue(String.class);
imageList.add(imageItems);
}
firebaseCallback.onCallback(imageList);
Log.i("imageList.size() is: ", String.valueOf(imageList.size()));
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.i(LOG_TAG, databaseError.getMessage());
}
};
mDatabaseImagesRef.addListenerForSingleValueEvent(valueEventListener);
}
called in onCreate()
readData(new FirebaseCallback() {
#Override
public void onCallback(List<String> list) {
mImageAdapter.notifyDataSetChanged();
}
});
getItemCount() in RecyclerViewAdapter
#Override
public int getItemCount() {
return imagesList.size();
}
To count the all the children beneath images_list node, please use the following lines of code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference imagesListRef = rootRef.child("images_list");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int count = (int) dataSnapshot.getChildrenCount(); //Cast long to int
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage());
}
};
imagesListRef.addListenerForSingleValueEvent(valueEventListener);
how could I then convert this number into an int value?
According to the official documentation, getChildrenCount() method returns a long and not an int. So you need to cast that primitive long to an int.
how to extract count from this block of code so that it can be referenced and used outside this code as well?
You cannot simply create the count variable as a global variable and use it outside the onDataChange() method because it will always be 0 due the asynchronous behaviour of this method. This means that if try to use its result outside this method, the data hasn't finished loading yet from the database and that's why is not accessible. With other words, your count will always be 0.
A quick solve for this problem would be to use the value of your count variable only inside the onDataChange() method, otherwise I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
try this
userReference = FirebaseDatabase.getInstance().getReference("images_list");
userReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
num = dataSnapshot.getChildrenCount();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
mRef= FirebaseDatabase.getInstance().getReference("images_list");
on your "onDataChange" method inside the listener , use getChildrenCount() to get the number of images, you can iterate those children using i loop , Exmeple :
mRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int count = dataSnapshot.getChildrenCount();
// here you get your images number
for(DataSnapshot ds : dataSnapshot.getChildren()) {
String key = ds.getKey()
// and here you get the key
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Try this code:
public void onDataChange(#NonNull DataSnapshot snap) {
int count = (int) snap.getChildrenCount();
//shows the number of items
}

Android Studio firebase database wait for read before adding new values

I have a database in which I store users and I want to check if a user exists before adding a new one so I don't overwrite.
I have a function that goes through database records and returns a boolean value if it finds or doesn't find the user.
public boolean checkUserExists(final String emailAddress, final String emailDomain){
DatabaseReference myRef = database.getReference("Users");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot mydata : dataSnapshot.getChildren()){
User user = mydata.getValue(User.class);
if (user.getEmailAddress().equals(emailAddress) &&
user.getEmailDomain().equals(emailDomain)){
userExists = true;
break;
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return userExists;
}
The way I am currently trying to do the check is like this:
if (!(registerRepo.checkUserExists(emailAddress, emailDomain))){
User user = new User(firsName, lastName, emailAddress, emailDomain);
registerRepo.writeUser(user);
} else {
Toast toast = Toast.makeText(getBaseContext(), "User exists", Toast.LENGTH_SHORT);
toast.show();
}
The problem is that it doesn't wait for the read and goes ahead and creates a new record (I'm using push so it creates the same record under a new push ID). I've seen that firebase has such a thing called transaction handler and I think that is what I need to use but the documentation didn't help me and I've looked at others asking sort-of the same question here but couldn't figure out a solution so please, if you can explain how to do it and not redirect me to other question I'd be grateful.
Firebase requests are asynchronous.
So you need to add a callback in your checkUserExists if you want to do some code after getting the result from database.
For example :
public interface OnCheckUserExist {
void exist();
void notExist();
}
registerRepo.checkUserExists(emailAddress, emailDomain, new OnCheckUserExist(){
#Override
public void exist(){
Toast toast = Toast.makeText(getBaseContext(), "User exists",Toast.LENGTH_SHORT);
toast.show();
}
#Override
public void notExist(){
User user = new User(firsName, lastName, emailAddress, emailDomain);
registerRepo.writeUser(user);
}
})
public void checkUserExists(final String emailAddress, final String emailDomain, OnCheckUserExist onCheckUserExist){
DatabaseReference myRef = database.getReference("Users");
myRef.addValueEventListener(new ValueEventListener() {
boolean userExist;
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot mydata : dataSnapshot.getChildren()){
User user = mydata.getValue(User.class);
if (user.getEmailAddress().equals(emailAddress) &&
user.getEmailDomain().equals(emailDomain)){
onCheckUserExist.exist();
userExist = true;
break;
}
}
if (!userExist){
onCheckUserExist.notExist();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
You need to put your code inside onDataChange like this
public boolean checkUserExists(final String emailAddress, final String emailDomain){
DatabaseReference myRef = database.getReference("Users");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot mydata : dataSnapshot.getChildren()){
User user = mydata.getValue(User.class);
if (user.getEmailAddress().equals(emailAddress) &&
user.getEmailDomain().equals(emailDomain)){
userExists = true;
break;
}
}
if (userExists) {
User user = new User(firsName, lastName, emailAddress, emailDomain);
registerRepo.writeUser(user);
} else {
Toast toast = Toast.makeText(getBaseContext(), "User exists", Toast.LENGTH_SHORT);
toast.show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return userExists;
}
Though I would suggest you to refactor this one by creating separate functions. :)

Categories