3

I'm working on a simple Android project which can insert and retrieve data from Firebase. The inserting function works well, which means the project has successfully connected to the Firebase database. However, the retrieving part doesn't work. I did many tests and think the problem is in the FirebaseHelper because when I tried to print the result of "firebasehelper.retrieveMajor()" in the Activity, it shows nothing. But it did show the data when printing data in FirebaseHelper. You can see the codes as followings.

Model:

@IgnoreExtraProperties
public class Major {

    public String major_id;
    public String major_name;

    public Major() {
    }

    public Major(String major_id, String major_name) {
        this.major_id = major_id;
        this.major_name = major_name;
    }

    public String getMajor_id() {
        return major_id;
    }

    public String getMajor_name() {
        return major_name;
    }
}

Adapter:

public class MajorListAdapter extends BaseAdapter {
    Context context;
    ArrayList<Major> majors;

    public MajorListAdapter(Context context, ArrayList<Major> majors) {
        this.context = context;
        this.majors = majors;
    }

    @Override
    public int getCount() {
        return majors.size();
    }

    @Override
    public Object getItem(int pos) {
        return majors.get(pos);
    }

    @Override
    public long getItemId(int pos) {
        return pos;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup viewGroup) {

        if(convertView==null)
        {
            convertView= LayoutInflater.from(context).inflate(R.layout.model,viewGroup,false);
        }

        TextView tv_majorid= (TextView) convertView.findViewById(R.id.tx_majorid);
        TextView tv_majorname= (TextView) convertView.findViewById(R.id.tx_majorname);

        final Major major= (Major) this.getItem(position);

        tv_majorid.setText(major.getMajor_id());
        tv_majorname.setText(major.getMajor_name());

        return convertView;
    }

}

FirebaseHelper:

public class FirebaseHelper {

    DatabaseReference db;
    Boolean saved=null;
    ArrayList<Major> majors = new ArrayList<>();

    public FirebaseHelper(DatabaseReference db) {
        this.db = db;
    }

    //Save the Major info. into db
    public Boolean saveMajor(Major major)
    {
        if(major==null)
        {
            saved=false;
        }else
        {
            try
            {
                db.child("Major").push().setValue(major);
                saved=true;

            }catch (DatabaseException e)
            {
                e.printStackTrace();
                saved=false;
            }
        }

        return saved;
    }

    private void fetchDataFromMajor(DataSnapshot dataSnapshot) {
        majors.clear();

        for (DataSnapshot ds : dataSnapshot.getChildren()) {
            Major major = ds.getValue(Major.class);
            majors.add(major);
        }

    }

    public ArrayList<Major> retrieveMajor() {

        db.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                fetchDataFromMajor(dataSnapshot);                  
            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                fetchDataFromMajor(dataSnapshot);

            }

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {

            }

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {

            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });


        return majors;
    }
}

The Activity that retrieves data and binds the data with the ListView:

public class MajorListActivity extends AppCompatActivity {

    DatabaseReference db;
    FirebaseHelper firebasehelper;
    MajorListAdapter adapter;
    ListView lv_MajorList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_major_list);

        lv_MajorList = (ListView) findViewById(R.id.lv_MajorList);

        db= FirebaseDatabase.getInstance().getReference();
        firebasehelper=new FirebaseHelper(db);

        //ADAPTER
        adapter = new MajorListAdapter(getApplicationContext(),firebasehelper.retrieveMajor());

        lv_MajorList.setAdapter(adapter);
    }

}
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
Haofan Hou
  • 31
  • 2

2 Answers2

1

Firebase APIs are asynchronous, meaning that they return immediately, before the results of your query are complete. Some time later, your callback will be invoked with the results. This means that your retrieveMajor function is returning whatever is in the array majors, before the query finishes.

You'll need to correctly handle the asynchronous nature of Firebase APIs, which means that you can't write methods that directly return data that you fetch from the database. You'll have to use the callbacks to wait for results, then update your UI as needed.

To learn more about why Firebase APIs are asynchronous and what to expect from them, read this article.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Thank you. I read some articles and found that onChildAdded() is actually a callback method. Why doesn't it work? – Haofan Hou Mar 09 '18 at 14:48
0

It prints the data because your ArrayList is being populated, but asynchronously, after you pass it to your Adapter.

The adapter holds a reference to the same ArrayList that is later getting populated via your Firebase callbacks, but the adapter itself needs to be notified using notifyDataSetChanged() when there is new data in the array, otherwise it won't check for it.

Basically, you need to find a way to notify your adapter of the dataset changing every time you get new data from Firebase. A simple mechanism to use would be a callback.

urgentx
  • 3,832
  • 2
  • 19
  • 30
  • Thank you. But onChildAdded() is a callback method which I used. Why doesn't it work? – Haofan Hou Mar 09 '18 at 14:49
  • I explain it in the second half of my answer. The callbacks return after the adapter is initialized with an empty ArrayList,. The adapter doesn't know there is more data after the callbacks return it, that's why the data is not being displayed on the screen. – urgentx Mar 11 '18 at 22:50
  • Tried many times using callbacks but still cannot implement it. Could you describe more specifically? – Haofan Hou Mar 15 '18 at 20:37
  • https://stackoverflow.com/a/47853774/4380236 This answer describes it pretty well I think – urgentx Mar 15 '18 at 21:46
  • Successfully implemented it. Thank you very much! – Haofan Hou Mar 16 '18 at 17:36