1

I've been looking at the firebase documentation and tried to make firebase work with an android app I'm building.

All the code examples I could find show how to get a value, then immediately print it within the firebase ValueEventListener inner class. I would like to get the values and store them in an array for later use.

What I try to do is when a user clicks the highscore button the client gets the current scoreboard from firebase, and sends that list to the highscoreboard class.

UPDATE: I have been able to retrieve the data now but there is a behavior that confuses me greatly. If the user clicks the highscorebutton the scores list gets populated and highscore class is correctly showing 10 elements. If I exit the scoreboard and enter it again it will now show all scores twice (understandably so, as I am just adding to the same list). But any calls to scores.clear() or scores = new ArrayList(); between the two clicks results in scores being empty even after the second click (I assumed that populating scores, then emptying it and repopulating it would leave 10 items in there) This behavior was the reason I thought my scores array never got populate when first posting here as I had a scores.clear() call inside the load function since I didn't want to duplicate values. If anyone is able to explain why this happens that would be fantastic.

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
ArrayList<Score> scores;
Firebase myFirebase;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Firebase.setAndroidContext(this);
    myFirebase = new Firebase(FirebaseURL);
    scores = new ArrayList<Score>();
    loadScoresFromFireBase();
}
public void loadScoresFromFireBase() {
    String entry = "";
    for (int i = 0; i < 10; i++) {
        entry = "Name_" + i;
        myFirebase.child("users").child(entry).addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot snapshot) {
                //getValue correctly returns a Score object
                scores.add(snapshot.getValue(Score.class));
            }

            @Override
            public void onCancelled(FirebaseError firebaseError) {

            }
        });

    }
}
 @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
   //....
    if ( id == 2) {
        Intent intent = new Intent(getApplicationContext(), ScoreBoard.class);
        Bundle bundle = new Bundle();
        //calling scores.clear() here leaves the scores array empty even though loadscores is called afterwards.
        loadScoresFromFireBase();
        Collections.sort(scores);
        bundle.putSerializable("players", scores);
        intent.putExtra("players", bundle);
        startActivityForResult(intent, 0);

    }
}
}
E. Hall
  • 181
  • 2
  • 10
  • 1
    "the `scores.add(s)` does not have any effect as it is called from an inner class" While it may be true that `scores.add(s)` doesn't do what you expect it to do, there is nothing in the current snippet that prevents it from working. The inner class can find the `scores` variable just fine, although you'll have to mark it as `final` if it's a local variable. Can you post a minimal, **complete** snippet that reproduces the problem. Likely a small class or a single function that contains everything. – Frank van Puffelen Mar 11 '16 at 00:43
  • Code / question updated. Turns out scores.add(s) was working just fine, but a call to scores.clear() inside the function was causing the arraylist to be empty and since I did the clear() call before the loop with scores.add(s) I thought the add call didn't do anything. Now I just can't figure out why populating and array, clearing it then populating it again leaves me with an empty array. – E. Hall Mar 11 '16 at 07:01

2 Answers2

4

You're falling for the classic asynchronous trap:

loadScoresFromFireBase();
Collections.sort(scores);

When you call loadScoresFromFireBase() (it's spelled Firebase btw), the program starts synchronizing the scores from the server. Loading this data takes some time. Instead of making your program wait (which would be a bad user experience), Firebase makes you pass in a ValueEventListener that it then calls when the data is available. But you're calling Collections.sort(scores) straight away, before the (first) data has been loaded.

You can most easily see this by adding a few log statements:

public void loadScoresFromFireBase() {
    String entry = "";
    for (int i = 0; i < 10; i++) {
        entry = "Name_" + i;
        myFirebase.child("users").child(entry).addValueEventListener(new ValueEventListener() {
            public void onDataChange(DataSnapshot snapshot) {
                System.out.println("Adding score to array");
                scores.add(snapshot.getValue(Score.class));
            }
            public void onCancelled(FirebaseError firebaseError) { }
        });

    }
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    //....
    if (id == 2) {
        Intent intent = new Intent(getApplicationContext(), ScoreBoard.class);
        Bundle bundle = new Bundle();
        System.out.println("Before calling loadScores");
        loadScoresFromFireBase();
        System.out.println("After calling loadScores");
        Collections.sort(scores);
        System.out.println("After sorting scores");

The output of these log statements will be:

Before calling loadScores

After calling loadScores

After sorting scores

Adding score to array

That is probably not what you expected. But it is completely normal when you're dealing with asynchronous/event driven code, like is common in modern internet programming.

The most direct solution is to move the code that needs the scores into the function that loads them:

public void loadScoresFromFireBase() {
    String entry = "";
    for (int i = 0; i < 10; i++) {
        entry = "Name_" + i;
        myFirebase.child("users").child(entry).addValueEventListener(new ValueEventListener() {
            public void onDataChange(DataSnapshot snapshot) {
                scores.add(snapshot.getValue(Score.class));
                Collections.sort(scores);
                Intent intent = new Intent(getApplicationContext(), ScoreBoard.class);
                Bundle bundle = new Bundle();
                bundle.putSerializable("players", scores);
                intent.putExtra("players", bundle);
                startActivityForResult(intent, 0);
            }
            public void onCancelled(FirebaseError firebaseError) { }
        });

    }
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    //....
    if (id == 2) {
        loadScoresFromFireBase();

Also see this answer I wrote a while ago: Setting Singleton property value in Firebase Listener

As well as these great answers on the same problem:

Although these are about JavaScript, they deal with the exact same problem.

Community
  • 1
  • 1
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
0

As you have not initialized scores you will be getting a NullPointerException when calling add

If you do

ArrayList<Score> scores = new ArrayList ();

it should work

Scary Wombat
  • 44,617
  • 6
  • 35
  • 64
  • I guess I should have been a bit more clear on that. As mentioned above the declaration it is initialized, I just didn't include the init code to avoid clutter. My problem is not a nullpointerexception, but that scores.size() stays at 0 no matter how many times I call scores.add(s) inside the inner class. – E. Hall Mar 11 '16 at 00:26
  • 1
    well if `scores.add(s);` is being called it must be adding to something - right? – Scary Wombat Mar 11 '16 at 00:32
  • Turns out it was, but a clear() call removed all stored values even though the clear was called before the add function was called. Question / code updated to reflect new discovery. Thank you for having a look.. – E. Hall Mar 11 '16 at 07:04