简体   繁体   中英

Issue using ScrollListener to pull data from Firebase Realtime DB when user scrolls to the bottom of a RecyclerView

I have an application that pulls object data from a Firebase Realtime DB, adds it to an ArrayList, which is then added to a RecyclerView. I utilize an onScrollListener on my RecyclerView so that when a user scrolls to the bottom of the RecyclerView, a function is called to pull more data from Firebase, add it to the ArrayList and notify the RecyclerViewAdapter of the Data set change.

When I run my application, it presents the initial pull of data in my RecyclerView, then immediately begins calling the function to fetch more data from my DB continuously until there is no more data left to pull and my RecyclerView is updated with this data; this occurs regardless of whether I scroll(in any direction/of any magnitude) or not.

Ideally, I would want the application to only pull data if the user scrolled to the bottom of the RecyclerView, and to do so one pull at a time. I'm having a very hard time debugging this issue as the logic seems to be correct; any help would be immensely appreciated.

Relevant global variables

RecyclerView rvVertical;
RecyclerViewAdapter rvaVertical;
long mTotalChildren = 0;
int mLastKey = 0;

ChildEventListener childEventListenerMain, childEventListenerPager;
boolean Loading = false;

private ArrayList<BlogObject> blogArray = new ArrayList<>();
private ArrayList<BlogObject> tempList = new ArrayList<>();

View loaded

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    final View view = inflater.inflate(R.layout.socialmediatab, container, false);

    rvVertical = (RecyclerView) view.findViewById(R.id.bloglistRecyclerView);
    Loading = true;
    Log.d(TAG, "onCreateView - Loading = " + Loading);
    firstEventListener();
    regAdapterVertical();

    addOnScrollListener();
    return view;
}

First function that gets called. initializes the first pull of data from Firebase

private void firstEventListener() {

    FirebaseDatabase.getInstance().getReference().child("blogs").addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            mTotalChildren = dataSnapshot.getChildrenCount();
        }
        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) { }
    });

    final Query BlogQuery = FirebaseDatabase.getInstance().getReference().child("blogs").orderByChild("createdAt").limitToLast(3);

    childEventListenerMain = new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot ds, String s) {
            Loading = true;
            Log.d(TAG, "First get blogs instance - Loading = " + Loading);
            BlogObject addblog = ds.getValue(BlogObject.class);

            if (addblog != null) {
                BlogObject addBlog = new BlogObject(ds.getValue(BlogObject.class).getImage(), ds.getValue(BlogObject.class).getTitle(), ds.getValue(BlogObject.class).getTimeStamp(), ds.getValue(BlogObject.class).getCreatedAt());
                blogArray.add(addBlog);
                rvaVertical.notifyDataSetChanged();
                mLastKey = blogArray.get(0).getCreatedAt();
                rvVertical.scrollToPosition(blogArray.size() - 1);
            }
        }
        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String s) { }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) { }

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

        @Override
        public void onCancelled(DatabaseError databaseError) { }
    };

    BlogQuery.addChildEventListener(childEventListenerMain);
    ValueEventListener BlogChildSingleListener = new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            Loading = false;
        }
        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) { }
    };

    BlogQuery.addListenerForSingleValueEvent(BlogChildSingleListener);
}

Adding the Scroll Listener to my Recycler View. Ideally, it is only supposed to fetch more data from Firebase if and only if the user scrolls to the bottom of the RecyclerView

private void addOnScrollListener() {
    rvVertical.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            Log.d(TAG, "Just Entered onScrolled and Loading = " + Loading);
            super.onScrolled(recyclerView, dx, dy);

            if(!rvVertical.canScrollVertically(1)) {
                LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                if (blogArray.size() >= 3 && !Loading && linearLayoutManager.findLastCompletelyVisibleItemPosition() == blogArray.size() - 1 && blogArray.size() < mTotalChildren) {
                    Loading = true;
                    getMoreBlogs();
                    Log.d(TAG, "Right After getMoreBlogs gets called. Loading = " + Loading);
                }
            }
        }
    });
}

Initializing Adapter with first pull of data

private void regAdapterVertical() {
    LinearSnapHelper linearSnapHelper = new SnapHelperOneByOne();
    linearSnapHelper.attachToRecyclerView(rvVertical);
    rvaVertical = new RecyclerViewAdapter(blogArray);
    rvVertical.setAdapter(rvaVertical);
    rvVertical.setLayoutManager((RecyclerView.LayoutManager) new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
}

This is my function to pull more data from Firebase. It gets called in my Scroll Listener

public void getMoreBlogs() {
    tempList.clear();

    Query getMoreQuery = FirebaseDatabase.getInstance().getReference().child("blogs").orderByChild("createdAt").endAt(mLastKey).limitToLast(3);
    childEventListenerPager = new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot ds, String s) {
            Loading = true;
            BlogObject blogObject = ds.getValue(BlogObject.class);
            if (blogObject != null) {
                BlogObject addBlog = new BlogObject(ds.getValue(BlogObject.class).getImage(), ds.getValue(BlogObject.class).getTitle(), ds.getValue(BlogObject.class).getTimeStamp(), ds.getValue(BlogObject.class).getCreatedAt());
                tempList.add(addBlog);
            }
        }

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

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) { }

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

        @Override
        public void onCancelled(DatabaseError databaseError) { }
    };

    getMoreQuery.addChildEventListener(childEventListenerPager);

    ValueEventListener addtoAdapter = new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            tempList.remove(tempList.size() - 1);
            blogArray.addAll(0, tempList);
            mLastKey = blogArray.get(0).getCreatedAt();
            rvaVertical.notifyDataSetChanged();
            Loading = false;
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };

    getMoreQuery.addListenerForSingleValueEvent(addtoAdapter);
}
}

This is my Logcat of Loading(boolean), that I kept track of throughout the program because I believe it was a main part of the issue. I have attached it below:

2019-05-30 10:26:14.590 24930-24970/com.yourgesture.newsocialmediatab I/FirebaseAuth: [FirebaseAuth:] Loading module via FirebaseOptions.
2019-05-30 10:26:14.956 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: onCreateView - Loading = true
2019-05-30 10:26:16.399 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: First get blogs instance - Loading = true
2019-05-30 10:26:16.406 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: First get blogs instance - Loading = true
2019-05-30 10:26:16.534 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Just Entered onScrolled and Loading = false
2019-05-30 10:26:16.539 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Right After getMoreBlogs gets called. Loading = true
2019-05-30 10:26:16.663 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Just Entered onScrolled and Loading = false
2019-05-30 10:26:16.666 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Right After getMoreBlogs gets called. Loading = true
2019-05-30 10:26:16.826 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Just Entered onScrolled and Loading = false
2019-05-30 10:26:16.828 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Right After getMoreBlogs gets called. Loading = true
2019-05-30 10:26:16.976 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Just Entered onScrolled and Loading = false
2019-05-30 10:26:16.977 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Right After getMoreBlogs gets called. Loading = true
2019-05-30 10:26:17.229 24930-24930/com.yourgesture.newsocialmediatab D/Social Media Tab: Just Entered onScrolled and Loading = false

In Your firstEventListener() ,

getReference().child("blogs") will actually fetches whole child data , which will be considered as read operation, even if you are not using it.

So there will be no any data saving in your approach.

Let me share one of the approach:

private void getUsers(String nodeId) {
    Query query;

    if (nodeId == null)
        query = FirebaseDatabase.getInstance().getReference()
                .child(Consts.FIREBASE_DATABASE_LOCATION_USERS)
                .orderByKey()
                .limitToFirst(mPostsPerPage);
    else
        query = FirebaseDatabase.getInstance().getReference()
                .child(Consts.FIREBASE_DATABASE_LOCATION_USERS)
                .orderByKey()
                .startAt(nodeId)
                .limitToFirst(mPostsPerPage);

    query.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            UserModel user;
            List<UserModel> userModels = new ArrayList<>();
            for (DataSnapshot userSnapshot : dataSnapshot.getChildren()) {
                userModels.add(userSnapshot.getValue(UserModel.class));
            }

            mAdapter.addAll(userModels);
            mIsLoading = false;
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            mIsLoading = false;
        }
    });
}

Three new variables are important in the above function: mPostsPerPage: It is the Integer constant defined in the MainActivity.java class which specifies how many records will be queried per page.

mIsLoading: It is a boolean defined in the MainActivity that is used to keep track of whether the Firebase Database query is in progress or not.

Consts.FIREBASE_DATABASE_LOCATION_USERS: It is the constant defined in the Consts.java class where simply the location of the “users” node is stored

Ref: https://blog.shajeelafzal.com/2017/12/13/firebase-realtime-database-pagination-guide-using-recyclerview/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM