Endless Scroll RecyclerView with dissimilar array sizes from API
Asked Answered
H

2

0

I'm currently using the scrollListener to enable endless scroll - you can find my question and Vilen's answer here:

Adding items to Endless Scroll RecyclerView with ProgressBar at bottom

This works wonders when you have the same dataset size each time you activate your adapter when you scroll For example, in Vilen's code, he is increasing the size of the dataset by the same amount, 15, each time he scrolls.

However, in the real world, this is often not the case. You have dissimilar dataset sizes all the time. In my example, I would like to do the following:

I would like to implement a geospatial search so that it returns a list of places closest to me. Now, if I'm sitting in a desert, my REST API call would return the places within 10km of me and this could just be 4 places (the camel hut, the pyramids, the watering hole and the coconut tree).

Populating 4 items in a recyclerview will not make the recyclerview long enough to allow the it to scroll further so that my app will trigger the API call again to find more places closer to me. The next 10km (20km from my current location) could be a town with lots more places to return, but because the recyclerview is not long enough to scroll, the API call is not made.

I have also made a tiny repo to demonstrate that the scroll listener is not triggered when the recyclerview is too short:

https://github.com/Winghin2517/DissimilarDataSetSizeRV.git

How can I get around this problem?

I almost feel like the scrolllistener should be triggered until the recyclerview fills up the screen but how can I determine when the screen will be filled up as there is no callback for recyclerview-fill-up-screen, as far as I know.

I had tried to stop changing the boolean loading to see that would help but without the boolean checking the status of the load, the progressBar will not necessarily be removed leading to this type of effect:

enter image description here

Hiatt answered 22/1, 2016 at 18:8 Comment(0)
H
0

Thanks to this thread, I was able to determine whether the recyclerview is long enough to scroll: How to know if a RecyclerView has enough content to scroll?

I then played with the code a little and it seems to work in this way.

I create two new variables:

final static String PROGRESS_BAR = "progressBar"; //this will be the unique object that will be added to the arraylist when you want to display the progress bar
boolean progressBarAdded; //the progressbar must only be added once so this will keep track of it when it is added.

   //adapter code is changed to take into account when the recyclerview can / cannot scroll
    mAdapter.setOnLoadMoreListener(new MyAdapter.OnLoadMoreListener() {
        @Override
        public void onLoadMore() {
            //add progress item
            //we can scroll and progress bar is not yet added
            if (!progressBarAdded && llm.findLastCompletelyVisibleItemPosition() != mAdapter.getItemCount() - 1) {
                progressBarAdded = true;
                myDataset.add(PROGRESS_BAR);
                mAdapter.notifyItemInserted(myDataset.size() - 1);
            }

            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //remove progress item
                    boolean removed = myDataset.remove(PROGRESS_BAR);
                    if (removed) {
                        mAdapter.notifyItemRemoved(myDataset.size());
                        progressBarAdded = false; //progress bar is now removed
                    }
                    //add items one by one
                    for (int i = 0; i < 3; i++) {
                        myDataset.add("Item" + (myDataset.size() + 1));
                        mAdapter.notifyItemInserted(myDataset.size());
                    }
                //we can scroll
                if(llm.findLastCompletelyVisibleItemPosition()!=mAdapter.getItemCount()-1) {
                    mAdapter.setLoaded();
                }
            }
        }, 500);

I then change the scrolllistener like this:

    if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    // End has been reached
                    // Do something
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    if (linearLayoutManager.findLastCompletelyVisibleItemPosition() != MyAdapter.this.getItemCount() - 1) { //determines whether the recyclerview is full or not. If not full, this will cause it to continue to load
                        loading = true;
                    }
                }
            }
        });
    }

Also make sure you take into account the new getItemType as each time it detects the string "progressBar", it must load the viewholder for the progressBar:

@Override
public int getItemViewType(int position) {
    return mDataset.get(position) != MainActivity.PROGRESS_BAR ? VIEW_ITEM : VIEW_PROG;
}
Hiatt answered 22/1, 2016 at 18:53 Comment(7)
So you solved the problem or do you still need help?Nonagon
solved. I can't believe i managed to solve it this quick. It took way less time than I thought it would take. I can't mark my answer as correct yet - i will do so in 2 hours time according to SO rules.Hiatt
Well if you have a better way to do it, you are free to post your solution. I feel my way is a bit hacky but it seems to work.Hiatt
I hope you won't consider me a rude person, but I couldn't fully understand your problem, but I think you are loading a data from server and populating it in your list, and if it has less items than it fills the screen you call next to fetch more data from server, and you want to stop your (On going loading animation) when there's no more data to fetch even if the list doesn't have enough items to fill the screen, Did I understand your request?Nonagon
Not exactly, if the recyclerview doesn't contain enough items to fill up the screen, the onLoadMoreListener is not going to be called as the recyclerview cannot be scrolled. onLoadMoreListener will only be activated when you can scroll the RV. So I need to ensure that the RV has enough items to scroll so that onLoadMore will be trigger when the user actually scrolls. Otherwise, if you are in the desert and you have 4 places around you, you will never be able to see further than these 4 places unless you take a trip to the town. With my solution,you can now see whats in the town from the desertHiatt
I have pushed my updated code to the repo, you can download my code and see if you understand now what I mean.Hiatt
Check the code I added as an answer, and in the code I added, onScroll (....) function will be called if the list view has no enough items to fill the screen, and then you can call next on your code to fetch more data / places.Nonagon
N
0

You could try this approach (The code below is from one of my apps, so you will have to fill the gaps, I just added the parts that shall solve your problem)>

public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener 
// Or use yourListView.setOnScrollListener(this);
{

    int preLast = -1;

    @Override
    public void onScroll(AbsListView lw, final int aFirstVisibleItemIndex, final int aVisibleItemCount, final int aTotalItemsCount) {
        int id = mListView.getId(); // Or use case android.R.id.list:     

        if(lw.getId() == id) {
                     // Sample calculation to determine if the last item is fully visible.
            final int lastItem = aFirstVisibleItemIndex + aVisibleItemCount;
            if(lastItem > 0 && lastItem == aTotalItemsCount) // lastItem > 0 helps to avoid continuous call if the list is empty.
            {
                if(preLast != lastItem) { //to avoid multiple calls for last item
                    preLast = lastItem;

                }
              }
        }
      }
}

@Override //it will be called before any calls to Adapter.getView(.....)
public void onScrollStateChanged(AbsListView view, int scrollState) {

}

And now and whenever you decide that there is no more data to fetch from your sever (to populate more items in your list) and you want to avoid the listview to keep calling / activating your (on going loading animation), you can simply set preLast value to lastItem value.

Nonagon answered 22/1, 2016 at 19:15 Comment(1)
I'm looking for recyclerview, not listview.Hiatt

© 2022 - 2024 — McMap. All rights reserved.