0

I'm using Firebase to save my data. I'm trying to separate Firebase methods and my methods on the activity. For example i have created class that called "FirebaseMethodsHelper" and there i want to write all the Firebase methods. For example, "getAllUsers" method that should return in list all the users. The only problem i have that it does not working.

I dont know what im doing wrong, so if you guys please can help me.

Fragment

  public class MyPlayListFragment extends Fragment {
    private FirebaseDatabase refToVideos;
    private FirebaseUser currentUser;
    private ArrayList<Video> videosList;
    private VideoViewAdapter adapter;
    private RecyclerView rvVideos;
    private List<Video> checkList;


public MyPlayListFragment() {
    // Required empty public constructor
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View v = inflater.inflate(R.layout.fragment_my_play_list, container, false);
    rvVideos = (RecyclerView)v.findViewById(R.id.rvVideos);

    return v;
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    videosList = new ArrayList<>();
    refToVideos = FirebaseDatabase.getInstance();
    currentUser = FirebaseAuth.getInstance().getCurrentUser();

    FirebaseMethodsHelper fmh = new FirebaseMethodsHelper();


    checkList = fmh.getAllVideosFromDB(currentUser);
    if(checkList != null)
    Log.d("checkList",checkList.toString());

FirebaseMethodHelper Class

   public class FirebaseMethodsHelper {
private FirebaseDatabase databaseRef;
private ArrayList<User> usersList;
private ArrayList<Video> videosList;



   public List<Video> getAllVideosFromDB(FirebaseUser currentUser){
        databaseRef = FirebaseDatabase.getInstance();
        databaseRef.getReference(Params.VIDEOS).child(currentUser.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for (DataSnapshot snapshot : dataSnapshot.getChildren()){
                    videosList.add(snapshot.getValue(Video.class));
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });


    return videosList;
  }
 }

I dont know why, but it always return null.

E.Bolandian
  • 553
  • 10
  • 35

2 Answers2

2

This is a classic problem with asynchronous web APIs: you cannot return something now that hasn't been loaded yet.

Firebase Database (and most modern web APIs) data is loaded asynchronously, since it may take some time. Instead of waiting for the data (which would lead to Application Not Responding dialogs for your users), your main application code continues while the data is loaded on a secondary thread. Then when the data is available, your onDataChange() method is called and can use the data.

This changes the flow of your app. The easiest way to see this is by placing a few log statements:

public List<Video> getAllVideosFromDB(FirebaseUser currentUser){
    databaseRef = FirebaseDatabase.getInstance();
    System.out.println("Before attaching listener");
    databaseRef.getReference(Params.VIDEOS).child(currentUser.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            System.out.println("Got data");
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            throw databaseError.toException(); // don't ignore errors
        }
    });
    System.out.println("After attaching listener");
}

Running this code will print the following sequence:

Before attaching listener

After attaching listener

Got data

This is probably not what you expected, but explains precisely why the array is empty when you return it.

The initial response for most developers is to try and "fix" this asynchronous behavior. I recommend against that: the web is asynchronous, and the sooner you accept that, the sooner you can learn how to become productive with modern web APIs.

I've found it easiest to reframe problems for this asynchronous paradigm. Instead of saying "First get all videos, then log them", I frame the problem as "Start getting all videos. When the videos are loaded, log them".

This means that any code that requires the video must be inside onDataChange() (or called from inside there). E.g.:

public List<Video> getAllVideosFromDB(FirebaseUser currentUser){
    databaseRef = FirebaseDatabase.getInstance();
    databaseRef.getReference(Params.VIDEOS).child(currentUser.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            for (DataSnapshot snapshot : dataSnapshot.getChildren()){
                videosList.add(snapshot.getValue(Video.class));
            }
            if (videosList != null) {
                Log.d("checkList",videosList.toString());
            }
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            throw databaseError.toException(); // don't ignore errors
        }
    });

}

As I said, this is a common problem for developers who haven't dealt with asynchronous APIs before. As such, there have been quite some questions on the topic already, and I recommend you check them out too:

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

It is returning null because the list videosList has not been initialized yet. Also, your approach for returning the list in your method is wrong. The firebase call is asynchronous, and thus you can't control when the data will be available, so almost everytime your method will return null(if the list has not been initialized) or empty list(in-case you initialized it).

A solution would be to use interface callback, this will return the list of videos to your fragment as desired:

public interface FirebaseCallback {
    void listVideos(ArrayList<Video> videos);
}

This method will be called from your FirebaseMethodHelper class:

public void getAllVideosFromDB(FirebaseUser currentUser, FirebaseCallback callback){
    databaseRef = FirebaseDatabase.getInstance();
    databaseRef.getReference(Params.VIDEOS).child(currentUser.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            ArrayList<Video> videosList = new ArrayList<>();

            for (DataSnapshot snapshot : dataSnapshot.getChildren()){
                videosList.add(snapshot.getValue(Video.class));
            }

            callback.listVideos(videosList);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    });  
}

And from your fragment, you implement the interface and pass this to the method like so:

public class MyPlayListFragment extends Fragment implements FirebaseCallback  {
    private FirebaseDatabase refToVideos;
    private FirebaseUser currentUser;
    private ArrayList<Video> videosList;
    private VideoViewAdapter adapter;
    private RecyclerView rvVideos;
    private List<Video> checkList;


public MyPlayListFragment() {
    // Required empty public constructor
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View v = inflater.inflate(R.layout.fragment_my_play_list, container, false);
    rvVideos = (RecyclerView)v.findViewById(R.id.rvVideos);

    return v;
}
@Override
public void listVideos(ArrayList<Video> videos) {
    //do what you want with the videos
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    videosList = new ArrayList<>();
    refToVideos = FirebaseDatabase.getInstance();
    currentUser = FirebaseAuth.getInstance().getCurrentUser();

    FirebaseMethodsHelper fmh = new FirebaseMethodsHelper();


    checkList = fmh.getAllVideosFromDB(currentUser, this);
    if(checkList != null)
    Log.d("checkList",checkList.toString());
riadrifai
  • 1,108
  • 2
  • 13
  • 26