20

I have one RecyclerView and I added list of data into the RecyclerView. I wanted to add more data in list, when last RecyclerView item is visible on screen. After that I want to make a web service call and update the RecyclerView data. How can I achieve this?

Any suggestions?

Bugs Happen
  • 1,835
  • 4
  • 25
  • 50
Rajesh Koshti
  • 581
  • 1
  • 7
  • 24

5 Answers5

19

One option would involve editing your LayoutManager. The idea here is to find the position of the last visible item. If that position is equal to the last item of your dataset, then you should trigger a reload.

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {

        final int result = super.scrollVerticallyBy(dy, recycler, state);

        if (findLastVisibleItemPosition() == mData.length - 1) {
           loadMoreData();
        } 

        return result;

    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        super.onLayoutChildren(recycler, state);

        if (findLastVisibleItemPosition() == mData.length - 1) {
           loadMoreData();
        } 
    }

Alternatively, you could do this via your adapter's onBindViewHolder method, although this is admittedly a bit of a "hack":

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == mData.length - 1) {
            // load more data here.
        }

        /// binding logic
    }

3rd option would be to add an OnScrollListener to the RecyclerView. @velval's answer on this page explains this well.

Regardless which option you go for, you should also include code to prevent the data load logic from triggering too many times (e.g., before the previous request to fetch more data completes and returns new data).

Gil Moshayof
  • 16,053
  • 4
  • 41
  • 54
  • I remember recyclerview will have couple of more viewholders binded which will be off the screen. So, I think checking for the last item may not help – cgr Jan 26 '16 at 09:59
  • it will bind it when the view is just about to become visible, so I believe it should be good enough for you. I will add another option tho – Gil Moshayof Jan 26 '16 at 10:03
  • 1
    Yes, it will bind when it is ABOUT to become visible. So, the last one that binded need not to have been visible already. So, we still need condition to check visibility. He may also try findLastVisibleItemPosition() in LinearLayoutManager. – cgr Jan 26 '16 at 10:52
  • accepatable answer but when i am scrolling it will called two times for webservice (first time on scroll and second last visible item in adapter ) – Rajesh Koshti Jan 27 '16 at 05:35
  • Hey @GilMoshayof please explain how can i use this your second option with LinearLayoutManager? – Rajesh Koshti Jan 27 '16 at 06:04
  • Well, like I said, you will need to implement safeguards to prevent sending a request twice. Perhaps you could hold a token indicating that a request has been sent when the dataset was at the current size, and if an attempt to load more items is made and the dataset is still in the same size, the attempt is ignored. – Gil Moshayof Jan 27 '16 at 08:40
  • @GilMoshayof can you help me with this one [link to my question](http://stackoverflow.com/questions/40726438/android-detect-when-the-last-item-in-a-recyclerview-is-visible) – Thorvald Olavsen Nov 25 '16 at 01:50
  • 2
    **Extremely incorrect answer**. `onBindViewHolder()` function is no place to _add/load_ data. Please do not use this technique. – Bugs Happen Feb 01 '19 at 07:42
  • @BugsHappen, could you please elaborate on this comment? I don't see the harm of queuing a server request to fetch more data once the final item in the data-source is bound. Of course, additional options were mentioned in this answer. Does your comment imply that these options are extremely incorrect as well? – Gil Moshayof Feb 01 '19 at 15:55
  • 1
    First method is a (excuse my language) disaster, because it is not the job of `onBindViewHolder` to look for more data. It should only bind data to the `ViewHolder`. The second method is some what better but, extending `LayoutManager` is still a greater job to do for such minor task. Why not simply use `RecyclerView.addOnScrollListener` and see where the scrolling has reached, like in the velval answer below. – Bugs Happen Feb 10 '19 at 09:25
  • @BugsHappen no worries re-the language. "Disaster" isn't really a slur. I'm afraid I have to disagree with your position though. To me the scroll answer seems a bit "hacky", and I still feel that binding the last item is the most natural way to do this. I suppose we'll just agree to disagree. I would, however, try to urge you to be a little more open minded. Only a Sith deals in absolutes ;) – Gil Moshayof Feb 10 '19 at 16:51
  • onBindViewHolder hack is definitely too much and not a good way in my opinion. I would recommend using a ScrollListener for this task. The question is clearly about paging or I understood wrong. ! – Farhan Jan 15 '20 at 03:19
10

If someone stumble across this post this is a quick and simple tutorial on how to do it:

All you need to do is:

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

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        int visibleItemCount        = lm.getChildCount();
        int totalItemCount          = lm.getItemCount();
        int firstVisibleItemPosition= lm.findFirstVisibleItemPosition();

        // Load more if we have reach the end to the recyclerView
        if ( (visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) {
            loadMoreItems();
        }
    }
});

Then your loadMoreItems() should look something like this:

private void loadMoreItems() {
    // init offset=0 the frist time and increase the offset + the PAGE_SIZE when loading more items
    queryOffset = queryOffset + PAGE_SIZE;
    // HERE YOU LOAD the next batch of items
    List<Items> newItems = loadItems(queryOffset, PAGE_SIZE);

    if (newItems.size() > 0) {
        items.addAll(newItems);
        adapter.notifyDataSetChanged();
    }
}
velval
  • 2,459
  • 27
  • 38
0
@Override
public void onBindViewHolder(final RecyclerView.ViewHolderholder, int position) {

    if (!list.isEmpty() || list== null) {

        if (position == (list.size() - 1)) {

            // add new data (add data to list)
            this.notifyDataSetChanged();

        }

    }


}
codebyjames
  • 487
  • 6
  • 9
0

Seen many of the above answers but my answer is different one and it will work in your cases also. My approach is based on scroll state of recylerview. Maintain below variable "check" and this should update only once when api responds. Put below code in your api response. If you want to handle last item only on every call of api.

final boolean[] check = {true};
    recyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            if (!recyclerView.canScrollVertically(1)) {
                // last item of recylerview reached.
                if (check[0]) {
                    //your code for last reached item
                    scroll_handler.setVisibility(View.GONE);
                }
            } else {
                scroll_handler.setVisibility(View.VISIBLE);
                check[0] = false;
            }
        }
    });

If you want to handle your last item every time then do it as below

recyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
    @Override
    public void onScrollChanged() {
        if (!recyclerView.canScrollVertically(1)) 
            // Bottom of recyler view.
            arrow_img.setRotation(180);
        }
    }
});
LuLuGaGa
  • 7,996
  • 4
  • 31
  • 43
0

See also Android - Detect when the last item in a RecyclerView is visible.

private fun isLastItemVisible(): Boolean {
    val layoutManager = recycler_view.layoutManager
    val position = layoutManager.findLastCompletelyVisibleItemPosition()
    return position >= adapter.itemCount - 1
}
CoolMind
  • 16,738
  • 10
  • 131
  • 165