1

I'm trying to get some database' child from Firebase database and put its values into ArrayList<MyClass> for using in Recyclerview.Adapter, but after the completion of data appending process in onDataChange method my list equals null.

I understand that it happens because of asynchronous behavior of onDataChange. Thanks to this answer How to return dataSnapshot value as a result of a method? I've created my FirebaseCallback interface and yes I do get my arrayList.

But I get it only inside its onCallback method. However, I need to pass the arrayList outside of that method to RecyclerView.Adapter.

Do I have to rebuild my RecylerView.Adapter and create that one inside the onCallback method? Please, can anybody explain me how to figure out my problem? Thanks a lot!

About my code:

I have a SentintelActivity that has to show the list of Sentinel using the RecyclerView. Sentinel is a class of entity that i fetch from FirebaseDatabase. Another class SentinelStorage is a singleton with main field List and a method readSentinelsListFromDB which has to return my list of Sentinels. At the moment that list equals null. In addition to code, I've attached a schema for more clearly understanding. enter image description here

My callback interface:

import java.util.List;
public interface FirebaseCallback {
    void onCallback (List<Sentinel> list);
}

My singleton class:

public class SentinelStorage { // Singleton class
    public static SentinelStorage sentinelStorage;
    private List<Sentinel> sentinelsList;

    ...
    public List<Sentinel> readSentinelsListFromDB(DatabaseReference dbRef, final FirebaseCallback firebaseCallback){
        final Sentinel sentinel = new Sentinel();
        ValueEventListener valueEventListener = new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                GenericTypeIndicator<Map<String, Object>> t = new GenericTypeIndicator<Map<String, Object>>(){};
                Map<String, Object> sentinelsMap = dataSnapshot.getValue(t);
                for (Map.Entry<String,Object> entry : sentinelsMap.entrySet()){
                    // convert every node of Map to Sentinel instance
                    sentinelsList.add(sentinel.mapToSentinel((Map)entry.getValue()));
                }
                firebaseCallback.onCallback(sentinelsList);
            }
            @Override
            public void onCancelled(DatabaseError databaseError) {
                Log.d(TAG,databaseError.getMessage());
            }
        };
        dbRef.addListenerForSingleValueEvent(valueEventListener);
        return sentinelsList; // here sentinelsList still is not empty
    }
    ...
}

SentinelActivity class:

public class ActSentinel extends BaseActivity implements FirebaseCallback{
    private static final String TAG = "# ActSentinel";

    RecyclerView mRecyclerView;
    SentinelViewAdapter adapter;
    private final String DBSentintelName = "sentinel";

    Sentinel sentinel = new Sentinel();
    FirebaseDatabase mFirebaseDatabase = FirebaseDatabase.getInstance();
    DatabaseReference dbRef = mFirebaseDatabase.getReference(DBSentintelName);
    List<Sentinel> list = new ArrayList<>();
    SentinelStorage sentinelStorage;

    @Override
    public void onCallback(List<Sentinel> lst) {
        list.addAll(lst);
        adapter.notifyDataSetChanged();
        Log.d(TAG, "INSIDE onCallback list empty :: "+list.isEmpty());
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sentinel_view);
        mRecyclerView = (RecyclerView)findViewById(R.id.recyclerView);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        adapter = new SentinelViewAdapter(list);

        sentinelStorage = SentinelStorage.get();
        //sentinelStorage.loadSentinelsListFromDB(dbRef);// testing with notnull list
        sentinelStorage.readSentinelsListFromDB(dbRef, this);

        mRecyclerView.setAdapter(adapter);

        Log.d(TAG, "!!!! list empty :: "+list.isEmpty());
    }

    public class SentinelViewHolder extends RecyclerView.ViewHolder{
        Sentinel sentinel;
        TextView tvLogin;
        TextView tvPassword;
        TextView tvName;
        TextView tvSurname;

        SentinelViewHolder(View view){
            super(view);
            tvLogin = (TextView) view.findViewById(R.id.login_value);
            tvPassword = (TextView) view.findViewById(R.id.password_value);
            tvName = (TextView) view.findViewById(R.id.name);
            tvSurname = (TextView) view.findViewById(R.id.surname);
        }

        public void bind (Sentinel sentinel){
            this.sentinel = sentinel;
            tvLogin.setText(sentinel.login);
            tvPassword.setText(sentinel.password);
            tvName.setText(sentinel.name);
            tvSurname.setText(sentinel.surname);
        }
    }

    public class SentinelViewAdapter extends RecyclerView.Adapter<SentinelViewHolder>{
        List<Sentinel> mSentinels;

        public SentinelViewAdapter(List<Sentinel> guards){
            mSentinels = guards;
        }


        public SentinelViewHolder onCreateViewHolder(ViewGroup container, int viewType){
            View view = LayoutInflater.from(container.getContext())
                    .inflate(R.layout.item_of_sentinel_table, container,false);                
            SentinelViewHolder vh = new SentinelViewHolder(view);
            return vh;
        }


        public void onBindViewHolder(SentinelViewHolder sentinelVH, int position){
            Sentinel sentinel = mSentinels.get(position);
            sentinelVH.bind(sentinel);
        }

        public int getItemCount(){
            return mSentinels.size();
        }
    }
}
Stanly T
  • 964
  • 1
  • 12
  • 30

1 Answers1

1

Your list has been passed to the adapter already, all you have to do is notify the adapter that something has changed in your datase. Also don't change the reference of it in the callback (assign another list to the variable list), simply call .addAll(). That way your adapter already knows about your list, it will simply need to be notified.

list = sentinelStorage.readSentinelsListFromDB(dbRef, new FirebaseCallback() {
        @Override
        public void onCallback(List<Sentinel> lst) {
            list.addAll(lst);
            adapter.notifyDatasetChanged();
            Log.d(TAG, "onCallback.Is list empty "+list.isEmpty());// not EMPTY
        }
    });

Moreover you don't need to make readSentinelsListFromDB return a List as it is already returned in the callback. so I'd remove that as well.

So call your method like this:

sentinelStorage.readSentinelsListFromDB(dbRef, new FirebaseCallback() {
        @Override
        public void onCallback(List<Sentinel> lst) {
            list.addAll(lst);
            adapter.notifyDatasetChanged();
            Log.d(TAG, "onCallback.Is list empty "+list.isEmpty());// not EMPTY
        }
    });

Another thing you could do to make your code cleaner is make your activity implement that interface:

public class ActivitySentinel extends BaseActivity implements FirebaseCallback

This will force you to create a method in your activity where you'll receive your callback calls:

@Override
public void onCallback(List<Sentinel> lst) {
   list.addAll(lst);
   adapter.notifyDatasetChanged();
   Log.d(TAG, "onCallback.Is list empty "+list.isEmpty());// not EMPTY
}

This way your method call would change to:

sentinelStorage.readSentinelsListFromDB(dbRef,this);
ColdFire
  • 6,764
  • 6
  • 35
  • 51
Levi Moreira
  • 11,917
  • 4
  • 32
  • 46