Android for loop not looping ? Firebase - android

I have an issue with the following code : I don't understand why my for-loop doesn't loop before if (gameExists[0] == false){... is called.
Button playButton = (Button) findViewById(R.id.play_button);
playButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
user = mFirebaseAuth.getCurrentUser();
final String gameCateg = String.valueOf(categorySelected[0]);
DatabaseReference allExistingGamesToMatchKeys = mFirebaseDatabase.getReference().child("gamestomatchkeys");
allExistingGamesToMatchKeys.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final boolean[] gameExists = {false};
Log.d("gameExists before loop ", String.valueOf(gameExists[0]));
for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
final String currentKey = postSnapshot.getKey();
//check player 2 is not the same as player 1 so that we don't match the same player
if(gameCateg.equals(postSnapshot.getValue().toString())){
mFirebaseDatabase.getReference().child("games").child(currentKey).child("player1").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String namePlayer1 = dataSnapshot.getValue(String.class);
if(!(user.getUid().toString().equals(namePlayer1))){
mFirebaseDatabase.getReference().child("gamestomatchkeys").child(currentKey).removeValue();
mFirebaseDatabase.getReference().child("games").child(currentKey).child("player2").setValue(user.getUid());
gameExists[0] = true;
Log.d("gameExists in for loop ", String.valueOf(gameExists[0]));
Intent intent = new Intent(getApplicationContext(), Game.class);
intent.putExtra("gameIdKey", currentKey);
startActivity(intent);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
break;
}
}
if (gameExists[0] == false){
Log.d("gameExists in if", String.valueOf(gameExists[0]));
This is what I get in my logs, in this order :
gameExists before loop: false
gameExists in if: false
gameExists in for loop: true
I don't understand why I get
gameExists in if: false
before
gameExists in for loop: true
I want my loop to be called and entirely looped before if (gameExists[0] == false){..., what should I modify ?
Thank you !

To make it simple, Firebase Database request are outside of the code flow. Take a look at this:
// here is first point
allExistingGamesToMatchKeys.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// here is second point
}
...
}
// here is third point
Above example will be executed like this
--> first point --> third point
Then where is the second point? Second point will be executed whenever Firebase get data from online database, so it is outside the flow (but most of the time, it will be executed after the third point.
So in conclusion, if you need some code to be executed after Firebase requests is done, place it inside onDataChange()
You might look at this for reference

It's because "gameExists in for loop" is not actually in the for loop. It's in a callback that is created in the for loop.
mFirebaseDatabase.getReference().child("games").child(currentKey).child("player1").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String namePlayer1 = dataSnapshot.getValue(String.class);
if(!(user.getUid().toString().equals(namePlayer1))){
mFirebaseDatabase.getReference().child("gamestomatchkeys").child(currentKey).removeValue();
mFirebaseDatabase.getReference().child("games").child(currentKey).child("player2").setValue(user.getUid());
gameExists[0] = true;
Log.d("gameExists in for loop ", String.valueOf(gameExists[0]));
Intent intent = new Intent(getApplicationContext(), Game.class);
intent.putExtra("gameIdKey", currentKey);
startActivity(intent);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Here you are creating a new instance of a ValueEventListener and you are overriding methods within it. Your are not executing the code within those methods at the point of instantiation. That code get called whenever your ValueEventListener decides to call the onDataChange() method.
I'm not entirely sure what this object is, or how it works:
mFirebaseDatabase.getReference().child("games").child(currentKey).child("player1")
But if I were you, I would start by seeing if I could get a DataSnapshot from it, and using that snapshot on whatever code you want to execute outside of the Listener.
If I had to guess, there might be a method like:
mFirebaseDatabase.getReference().child("games").child(currentKey).child("player1").getDataSnapshot();
that you can call. Then copy all the code from inside onDataChange() to outside of the listener.
(I have no idea if that method exits, but I would assume there is some way of getting the current DataSnapshot)

Related

Placing Firebase value event callbacks in a single place

I often find myself writing this piece of code again and again in multiple activities when using firebase realtime db:
ValueEventListener v =new ValueEventListener() {
#Override
public void onDataChange (#NonNull DataSnapshot dbSnapshot){
String ourKey="";
String ourValueID="";
for (DataSnapshot childSnap : dbSnapshot.getChildren()) {
String childKey = childSnap.getKey();
if (childKey == null) {
//do some stuff 1 // and break/Continue/return
}
//or we can directly do something here, as we already assured key is present
else if(childKey.equals(ourKey)){
//do some stuff 2 // and break/Continue/return
MyClass myClass =childSnap.getValue(MyClass.class);
if(myClass==null){
//do some stuff 3 // and break/Continue/return
}
else if(myClass.getID().equals(ourValueID)){
//do some stuff 4 // and break/Continue/return
}
else {
//do some stuff 5 // and break/Continue/return
}
}
else {
//do some stuff 6 // and break/Continue/return
}
}
}
#Override
public void onCancelled (#NonNull DatabaseError databaseError){
//do some stuff 7
}
};
although this is suppose to be how firebase works, it makes my code a lot more unreadable and difficult to debug. what could be a good approach to use these callbacks in a way, that i write this code once and de-clutter my code base?An example would be great.
Inside onDataChange(), you can just call a method:
ValueEventListener v =new ValueEventListener() {
#Override
public void onDataChange (#NonNull DataSnapshot dbSnapshot){
String ourKey="";
String ourValueID="";
retrieveDataFromFb(dbSnapshot);
public void retrieveDataFromFb(DataSnapshot dataSnapshot){
for (DataSnapshot childSnap : dbSnapshot.getChildren()) {
String childKey = childSnap.getKey();
if (childKey == null) {
//do some stuff 1 // and break/Continue/return
}
else if(childKey.equals(ourKey)){
MyClass myClass =childSnap.getValue(MyClass.class);
}
}
From what I understand you want to store all DB methods in a separate class so that you can reuse these methods which would make the code look cleaner and you are trying to get callback values when they get returned from firebase.
There can be many ways to handle callbacks for events what I recommend is to use an interface it will modularize your code and make it look cleaner, so what you can do is to store the DB methods in a separate class (say FirebaseDB), create your methods there and use an interface to get the callbacks. An example on how you can do it:-
Create an Interface either in the class or separate from the class
public class FirebaseDB {
//This is your interface
public interface DBCallbacklistner {
void onCallback(Map<String, Object> keyMap);
}
public void getkeys(String any_value_you_need_to_pass, DBCallbacklistner dbCallbacklistner){
//I have used a different method here you can use your releveant method here
database.somemethod(any_value_you_need_to_pass, new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot) {
//Suppose you receive the callback here
if(documentSnapshot.exists()){
Map<String, Object> keysMap = (HashMap<String, Object>) documentSnapshot.getData();
//Pass the callback in your interface
dbCallbacklistner.onCallback(keysMap);
}
}
});
}
}
Use that interface wherever you want
Using the function from the class call that interface and use the values
mFirebaseDBObject.getkeys(value, new FirebaseDB.DBCallbacklistner() {
#Override
public void onCallback(Map<String, Object> keyMap) {
if (keyMap != null) {
//Use your keymap here
}
}
});
One more thing I want to point out is that If there are too many callbacks for different calls, I suggest to make separate interfaces based on logical seperation of callbacks.
Because if there are many callbacks in a single interface you would have to override each one of them, whether you require it or not.
For the time being, i am using the following approach:
Suppose my firebase db consists of a list of objects which can be deserialised to the following format:
class MyClass{
public String myClassUniqueID;
... other attributes;
}
For the db i will be handling all value event listener's lifecycle in my own activity(i.e attaching to the db refernce via dbRef.addValueEventListener(dbListener); or dbRef.removeEventListener(dbListener);, But the process of creating this dbListener and passing it the neccessary tasks to be done would be managed in the following utility function :
public interface DbListenerActions {
void onMyClassObjFound(#NonNull MyClass matchedObj);
default void onMyClassObjNOTFound() {
}
}
public static ValueEventListener getMyClassObjectFinderListener(String id, DbListenerActions actions) {
Log.e(TAG, "onDataChange: our id:" + id);
ValueEventListener dbListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dbSnapshot) {
for (DataSnapshot currChildSnap : dbSnapshot.getChildren()) {
String currChildKey = currChildSnap.getKey();
MyClass currChildValue = currChildSnap.getValue(MyClass.class);
if (currChildKey == null) {
Log.e(TAG, "onDataChange: currChildKey is null. continuing");
continue;
}
if (currChildValue == null) {
Log.e(TAG, "onDataChange: currChildValue is null.continuing");
continue;
}
if (currChildValue.myClassUniqueID.equals(id)) {
Log.e(TAG, "onDataChange: currChildValue id matches our id ");
Log.e(TAG, "onDataChange: performing action and RETURNING(i.e getting out of this callback)");
//do stuff here
actions.onMyClassObjFound(currChildValue);
return;
} else {
Log.e(TAG, "onDataChange: current obj DOES NOT matches our id. continuing");
Log.e(TAG, "onDataChange: current object ID :" + currChildValue.myClassUniqueID);
Log.e(TAG, "onDataChange: --------------------------------------------------------------");
continue;
}
}
Log.e(TAG, "onDataChange: user not found, performing not found action" );
actions.onMyClassObjNOTFound();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
return dbListener;
}
In this way, i am able to get the necessary log info that i want during my debugging and since there are only 2 possible actions that i want to perform, i get a lot more assurance on the working of my listener. 50 lines of necessary but redundant code from 11 activities combined to just 1 utility function!
Now all i need to write is this small , more easy to debug piece of code in each of my activity:
ValueEventListener dbListener=getMyClassObjectFinderListener("some_id", new DbListenerActions() {
#Override
public void onMyClassObjFound(#NonNull MyClass matchedObj) {
//callSomeFunction()
// callSomeOtherFunction(matchedObj)
//...
}
});
Since i made the onMyClassObjNOTFound(..) function default i don't even need to provide that unless i really want to perform some action there. So this whole thing is working nicely for me :D
I asked on twitter about this thing too, somebody told me that an abstract class could also be used for such thing. I didn't got to research more there, but if someone knows about that approach too, then let me know!

How do I check if specific child value exists in FireBase (Android)

I have some trouble trying to check if user information is stored already in the FireBase database.
Basically I'm trying to do something stupid like this:
"select user_name from user where user_id="+userID+"
And if the nickname exists it should make the boolean var isFirstTime = false and if it doesn't it should stay true. And after that it should show register box or not.
This is my db:
Firebase
And this is my code in onCreate method:
databaseReference = FirebaseDatabase.getInstance().getReference();
DatabaseReference dbRefFirstTimeCheck = databaseReference.child("User").child(user.getUid()).child("Nickname");
isFirstTime = true;
dbRefFirstTimeCheck.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.getValue() != null) {
isFirstTime=false;
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
if(isFirstTime) {
showNewUserBox();
}
else {
}
No matter what I do, the methor showNewUserBox() is being called. How do I get the data i need and check if it's there?
As others have commented, data is loaded from Firebase asynchronously. By the time you check isFirstTime, the data hasn't been loaded yet, onDataChange hasn't been run yet, so ifFirstTime will have its default value (false for a boolean).
All code that requires data from the database should be inside onDataChange (or invoked from within there). The simplest fix for your code is:
databaseReference = FirebaseDatabase.getInstance().getReference();
DatabaseReference dbRefFirstTimeCheck = databaseReference.child("User").child(user.getUid()).child("Nickname");
dbRefFirstTimeCheck.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()) {
showNewUserBox();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException(); // don't ignore errors
}
});
Also see some of the many questions about asynchronous loading from Firebase, such as getContactsFromFirebase() method return an empty list (or this quite old classic: Setting Singleton property value in Firebase Listener).

How to stop Firebase database infinite looping

I am creating an app and part of it's features in user interaction. I want to store user comments on a post and I do not want to limit the amount of comments any one user can make. I do this by assigning a random number as the .setValue of the database entry.
With this implementation, whenever the comment is sent to the database it is stuck in an infinite loop where it will continually update with the same string entered in the text box but it will constantly generate new posts.
Full code;
sendComment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
commitPost();
finish();
}
});
}
private void commitPost() {
commentProgress.setMessage("Posting");
commentProgress.show();
final String commentInput = commentText.getText().toString().trim();
if(TextUtils.isEmpty(commentInput)){
Snackbar.make(commentLayout,"You haven't finished your post yet",Snackbar.LENGTH_SHORT);
}else{
commentDB.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String uniqueId = UUID.randomUUID().toString();
commentDB.child(postID).child("Comments").child(uniqueId).setValue(commentInput);
commentProgress.dismiss();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
The problem lies in the commentDB.addValueEventListener
The problem is that you set a listener for data changes and you also change data inside it (so it is called).
You don't need the listener, just add:
else {
String uniqueId = UUID.randomUUID().toString();
commentDB.child(postID)
.child("Comments")
.child(uniqueId)
.setValue(commentInput);
commentProgress.dismiss();
}

Working with EventListener in Firebase for Android

Consider, I store the following Key-Value pair in Firebase Database.
Key: "CarBrand" and Value: "Audi"
I read the Value for this Key from Firebase Database and display the Value in TextView. For this, I use an EventListener.
The problem I face with an EventListsner is since it runs on a separate thread, the TextView returns a NullPointerException even before the Value is fetched from Firebase.
To overcome the issue I have been using this dirty trick (using a Handler with 500 to 1000 ms delay). Can someone guide me the right way to fetch and display the Value in TextView or any other Views
PS: Apologies for this codeless Question
Thanks
EDIT: More the Code, better the question. The sample code is as follows
String CardBrand;
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
CarBrand = dataSnapshot.child("CarBrand").getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
mTextView.setText(CarBrand);
The above code results in error. To avoid this, I replaced the last line of code with
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mTextView.setText(CarBrand);
}
}, 1000);
So this is what I have been doing. What would be the right way to display the Text in TextView in my Case?
Your approach is wrong as the listener is updating the String CarBrand But since the code is not in sync and casing the Error. You need to update the code
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
String CarBrand =
dataSnapshot.child("CarBrand").getValue(String.class);
updateBrand(CarBrand);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
you need to create a Function to update the value
/* updates the Car brand Text
TODO:: You can do anystuff you want to do after anything gets
updated
*/
public void updateBrand(String brand){
mTextView.setText(CarBrand);
}
NOTE : Put Listener on the Data you are actually looking for changes.
If you want to work with the result of some callback (like ValueEventListener) you should execute your code inside the overriding methods.
String cardBrand;
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
carBrand = dataSnapshot.child("CarBrand").getValue(String.class);
mTextView.setText(carBrand);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
Let me show you small example:
This class responsible for retreiving the link for you, and it doesn't know what will you do with it, so it takes Callback and pass the result to your callback, where you can do anything you want.
public class SomeDataSource {
void getData(Callback callback) {
String url = Backend.GetUrl();
callback.onDownloadUrlReceived(url);
}
}
this is description of callback. Which you can implement to handle data from SomeDataSource
public interface Callback {
void onDownloadUrlReceived(String url)
}
usage
void onCreate(Bundle savedInstanceState) {
SomeDataSource downloader = new SomeDataSource();
//here callback is implemented, and you can handle data from SomeDataSource like you want.
downloader.getData(new Callback() {
#Override
public void onDownloadUrlReceived(String url) {
do wat you want with url
}
});
}

Firebase Database Datasnapshot

I have a firebase database from which I save and retrieve data from, to and from. I know how datasnapshot works inside an addValueEventListener. The problem is that this is only called or triggered when the firebase database detects change in its data. I only want to access data and read it to be able to store it in an arraylist or the same thing.
I have a code like this:
public void foo(){
DatabaseReference x= FirebaseDatabase.getInstance().getReference().child("x");
reservations.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String userID = client.getId();
for(DataSnapshot snap : dataSnapshot.getChildren()){
if(snap.child("someId").equals(someId)) number++;
if(snap.child("userID").getValue().equals(client.getId())){
isAlreadyReserved = true; // if user has already reserved the item
alreadyReserved();
break;
}
Log.e("isAlreadyReserved: ", isAlreadyReserved+"");
numberOfReservations++;
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
if(isAlreadyReserved) {
alreadyReserved();
}
else if(number == numberOfCopies){
// material is no longer available
OtherActivity.showMaterialUnavailable();
}
else{
Reservation reservation = new Reservation();
reservation.setBookId(this.bookId);
reservation.setResID((numberOfReservations+1)+"");
reservation.setUserID(client.getId());
String key = reservations.push().getKey();
reservations.child(key).setValue(reservation);
Log.e("Reservations: ", "reserve successful");
AlertDialog.Builder builder = new AlertDialog.Builder(this.context);
builder.setTitle(R.string.reservationSuccess_title)
.setMessage(R.string.reservationSuccess_body);
AlertDialog dialog = builder.create();
dialog.show();
}
}
You can see that inside onDataChange I only count materials and set some flags, which I can supposedly do outside the ValueEventListener.
But I notice that this is faulty because onDataChange is called only when writing to the Firebase database occurs. Which should not be the case.
What can I do to loop through the values inside the DatabaseReference x without calling onDataChange, or without using DataSnapshot?
You cannot loop inside a DatabaseReference without using a listener. When we are talking about Firebase, we are talking only about listeners. So in order to get those values, you need to use a listener and than get the data out from the dataSnapshot.
What i think your problem is in your case, is that onDataChange method is called in an asynchronously way. This means that everything you are doing outsite this method is actually executed before onDataChange method has been called. So in order to understand what is actually going on, please see this post and this post. Reading this posts, will teach you how to query data in Firebase and how to retrieve data from Firebase asynchronously.
Hope it helps.
In order to get the values of DatabaseReference x, you should use addListenerForSingleValueEvent
x.addListenerForSingleValueEvent(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
//do something
}
#Override
public void onCancelled(DatabaseError databaseError)
{
//do something
}
});
as mentioned in the firebase documentation:
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.

Categories

Resources