0

I have a Fragment in which I fetch some blogs as soon as it loads. I am doing pagination here, so, I load data for page=1 initially.

I also have a scrollListener implemented which makes subsequent API calls to load more blogs as user scroll downs. Every time a new call is made, I add a progressBar at the end of the recyclerview. The code is given below.

The issue that is happening is, I am getting multiple progressdialogs as shown in the image Multiple Progress Dialogs. Also, even the page number keeps increasing and it keeps on loading more data. Without adding line numbers (1-4), there is no issue of endless data coming. I just don't get progressdialog.

I just don't understand the issue with the code. I simply want to add progressdialog at the end of recyclerview whenever new API call is made.

    BlogsRecyclerViewAdapter adapter;
    ArrayList<BlogResponse> blogsList;

// Code for scrollListener in Fragment

    blogsList = new ArrayList<>();
    adapter = new BlogsRecyclerViewAdapter(getContext(), blogsList);
    blogsRecyclerView.setAdapter(adapter);
    final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
    blogsRecyclerView.setLayoutManager(linearLayoutManager);

    scrollListener = new BlogsRecyclerViewScrollListener(linearLayoutManager) {
        @Override
        public boolean onLoadMore(final int page, int totalItemsCount, RecyclerView view) {

            blogsList.add(null);  //line-1
            adapter.notifyItemInserted(blogsList.size()-1); //line-2

            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //   remove progress item
                    blogsList.remove(blogsList.size() - 1); //line-3
                    adapter.notifyItemRemoved(blogsList.size()); //line-4
                    fetchBlogs(page);
                }
            }, 2000); //time 2 seconds
            return true; // ONLY if more data is actually being loaded; false otherwise.
        }
    };
    fetchBlogs(1);
   blogsRecyclerView.addOnScrollListener(scrollListener);

// BlogsRecyclerViewScrollListener

 public abstract class BlogsRecyclerViewScrollListener extends RecyclerView.OnScrollListener{
private int visibleThreshold = 3;

private int currentPage = 1;

private int previousTotalItemCount = 0;

private boolean loading = true;

private int startingPageIndex = 1;

RecyclerView.LayoutManager mLayoutManager;

public BlogsRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
    this.mLayoutManager = layoutManager;
}

public int getLastVisibleItem(int[] lastVisibleItemPositions) {
    int maxSize = 0;
    for (int i = 0; i < lastVisibleItemPositions.length; i++) {
        if (i == 0) {
            maxSize = lastVisibleItemPositions[i];
        }
        else if (lastVisibleItemPositions[i] > maxSize) {
            maxSize = lastVisibleItemPositions[i];
        }
    }
    return maxSize;
}

@Override
public void onScrolled(RecyclerView view, int dx, int dy) {
    int lastVisibleItemPosition = 0;
    int totalItemCount = mLayoutManager.getItemCount();

    lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();

    if (totalItemCount < previousTotalItemCount) {
        this.currentPage = this.startingPageIndex;
        this.previousTotalItemCount = totalItemCount;
        if (totalItemCount == 0) {
            this.loading = true;
        }
    }

    if (loading && (totalItemCount > previousTotalItemCount)) {
        loading = false;
        previousTotalItemCount = totalItemCount;
    }

    if (!loading && (lastVisibleItemPosition + visibleThreshold) >= totalItemCount) {
        currentPage++;
        onLoadMore(currentPage, totalItemCount, view);
        loading = true;
    }
}
    public void resetState() {
        this.currentPage = this.startingPageIndex;
        this.previousTotalItemCount = 0;
        this.loading = true;
    }

    public abstract boolean onLoadMore(int page, int totalItemsCount, 
    RecyclerView view);
}

// fetch Blogs method

     private void fetchBlogs(final int page) {

        apiServiceWithoutVersion.getBlogs(String.valueOf(page))
        .enqueue(new 
        Callback<ArrayList<BlogResponse>>() {
        @Override
        public void onResponse(Call<ArrayList<BlogResponse>> call, 
        Response<ArrayList<BlogResponse>> response) {
            if(response.isSuccessful()){  
                for(BlogResponse blogResponse: response.body()){
                    blogsList.add(blogResponse);
                }
                adapter.notifyDataSetChanged();
            }else{
                //some code to show error
            }
        }

        @Override
        public void onFailure(Call<ArrayList<BlogResponse>> call, Throwable t) {
            //some code to show failure
        }
    });
}

// item_loading.xml

  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout 
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
    <ProgressBar
        android:id="@+id/progress_bar_at_bottom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />
 </LinearLayout>

// BlogsRecyclerViewAdapter

public class BlogsRecyclerViewAdapter extends 
RecyclerView.Adapter<RecyclerView.ViewHolder> {

Context mContext;
ArrayList<BlogResponse> mObjects;
ArrayList<BlogResponse> mFilteredObjects;
onItemClickListener mListener;

private final int VIEW_ITEM = 1;
private final int VIEW_PROG = 0;

public void setOnItemClickListener(onItemClickListener listener){
    mListener = listener;
}

public BlogsRecyclerViewAdapter(Context context, ArrayList<BlogResponse> objects){
    mContext = context;
    mObjects = objects;
    mFilteredObjects = objects;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    RecyclerView.ViewHolder holder = null;
    if(viewType==VIEW_ITEM) {
        View view = View.inflate(mContext, R.layout.list_item_blogs_recycler_view, null);
        holder = new BlogViewHolder(view);
    }else{
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_loading, parent, false);

        holder = new ProgressViewHolder(v);
    }
    return holder;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

    if(holder instanceof BlogViewHolder){
        //code for blogs

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mListener.onItemClick(position);
            }
        });
    }else{
        ((ProgressViewHolder)holder).progressBar.setIndeterminate(true);
    }

}

@Override
public int getItemCount() {
    return mFilteredObjects.size();
}

private class BlogViewHolder extends RecyclerView.ViewHolder {

    public BlogViewHolder(View itemView) {
        super(itemView);
        // Code for blogs
    }
}

private class ProgressViewHolder extends RecyclerView.ViewHolder {
    public ProgressBar progressBar;
    public ProgressViewHolder(View v) {
        super(v);
        progressBar = v.findViewById(R.id.progress_bar_at_bottom);
    }
}

public interface onItemClickListener{
    void onItemClick(int position);
}


@Override
public int getItemViewType(int position) {
    return mObjects.get(position)!=null? VIEW_ITEM: VIEW_PROG;
}

}

Yesha
  • 666
  • 5
  • 21

3 Answers3

1

you may try to check if data is currently loading

if(blogsList.get(blogsList.size() - 1) != null) {
    blogsList.add(null);  //line-1
    adapter.notifyItemInserted(blogsList.size()-1); //line-2

    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            //   remove progress item
            blogsList.remove(blogsList.size() - 1); //line-3
            adapter.notifyItemRemoved(blogsList.size()); //line-4
            fetchBlogs(page);
        }
    }, 2000); //time 2 seconds
}
Oleksandra
  • 431
  • 4
  • 11
  • Thank you for your answer. It does solve the problem to some extent that I am not getting multiple progress dialogs now but I am still getting endless API calls. It is not able to identify that last call/page. I think there is some issue with items getting added and removed and at the same time the check that gets moe data if (!loading && (lastVisibleItemPosition + visibleThreshold) >= totalItemCount) – Yesha Apr 13 '18 at 05:55
  • @Yesha I think it'd be better to add some boolean variable for tracking api call execution and set it true on call started and false in your public void onResponse(..). And if it's true at this moment, just ignore onScrolled stuff and don't execute onLoadMore(..) – Oleksandra Apr 13 '18 at 07:46
  • Maybe the problem is in this line: int totalItemCount = mLayoutManager.getItemCount(); when you add null item to your list, this will be true: totalItemCount > previousTotalItemCount. You may try to add ProgressBar as a separate view below your RecyclerView, then bound Recycler to it and make it GONE when data is not loading. So you won`t have to add null item to your list – Oleksandra Apr 18 '18 at 07:51
  • Also I don't get why do you use postDelayed(.., 2000) and wait for 2 seconds before executing api call. You add progressBar, then after 2 secs remove it and then start api call? You should show PB, then do api call and hide PB on your onResponse() method – Oleksandra Apr 18 '18 at 07:55
  • One more thing you don't need to do use foreach to add new data, you may use blogsList.addAll(response.body()); – Oleksandra Apr 18 '18 at 07:57
0

In your case some ware your onLoadMore() called multiple times before getting your response. so, it add null in your list so you show multiple progressBar in your list.

So, you have to debug after scroll more.

Mehul Kabaria
  • 5,328
  • 4
  • 20
  • 42
0
// The minimum number of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 0;

change or adjust this visibleThreshold as per list item view height. You can check this link for some more details. This will help you to stop multiple API calls on scroll.

Hitesh Sarsava
  • 594
  • 3
  • 10