Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recyclerview - cannot call this method in a scroll callback

I have seen this question many times in StackOverflow, but I couldn't get the issue fixed. So reaching out for help.

Please note: I have already checked other questions on SO and though I could get rid of the error, there are other issues with it.

Here is my existing code:

My Adapter class:

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

        Context context;

        private final int VIEW_TYPE_ITEM = 0;
        private final int VIEW_TYPE_LOADING = 1;
        private IOnLoadMoreListener mIOnLoadMoreListener;

        private int visibleThreshold = 3;
        private int lastVisibleItem, totalItemCount;
        private int[] lastVisibleItems = new int[mStaggeredLayoutManager.getSpanCount()];

        public RecyclerViewAdapter(Context context) {
            this.context = context;

            final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) recyclerView.getLayoutManager();

            recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);

                    totalItemCount = staggeredGridLayoutManager.getItemCount();
                    lastVisibleItems = staggeredGridLayoutManager.findLastVisibleItemPositions(new int[mStaggeredLayoutManager.getSpanCount()]);

                    if (staggeredGridLayoutManager.getSpanCount() == 1) {
                        lastVisibleItem = lastVisibleItems[0];
                    } else if (staggeredGridLayoutManager.getSpanCount() == 2) {
                        lastVisibleItem = Math.max(lastVisibleItems[0], lastVisibleItems[1]);
                    } else if (staggeredGridLayoutManager.getSpanCount() == 3) {
                        lastVisibleItem = Math.max(Math.max(lastVisibleItems[0], lastVisibleItems[1]), lastVisibleItems[2]);
                    }

                    if (!isRefreshing && totalItemCount <= lastVisibleItem + visibleThreshold) {

                        if (mIOnLoadMoreListener != null) {
                            mIOnLoadMoreListener.onLoadMore();
                        }

                        isRefreshing = true;
                    }
                }
            });

        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

            if (viewType == VIEW_TYPE_ITEM) {

                View v = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_recyclerview_shop_fragment, parent, false);
                return new CustomViewHolder(v);
            } else if (viewType == VIEW_TYPE_LOADING) {

                View v = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_recyclerview_loading_shop_fragment, parent, false);
                return new LoadingViewHolder(v);
            }

            return null;
        }

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

            Product item = listItems.get(position);

            if (holder.getItemViewType() == VIEW_TYPE_ITEM) {

                CustomViewHolder customViewHolder = (CustomViewHolder) holder;
                Glide.with(context).load(item.getFeaturedSrc()).into(customViewHolder.imageView);

            } else if (holder.getItemViewType() == VIEW_TYPE_LOADING) {

                if (holder instanceof LoadingViewHolder) {
                    LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;
                    loadingViewHolder.progressBar.setIndeterminate(true);
                }
            }


        }

        @Override
        public int getItemCount() {
            return listItems == null ? 0 : listItems.size();
        }

        @Override
        public int getItemViewType(int position) {

            if (listItems.get(position) == null) {
                return VIEW_TYPE_LOADING;
            } else {
                return VIEW_TYPE_ITEM;
            }
        }

        public void setOnLoadMoreListener(IOnLoadMoreListener mIOnLoadMoreListener) {
            this.mIOnLoadMoreListener = mIOnLoadMoreListener;
        }


        public class CustomViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

            ImageView imageView;
            TextView textView;

            String id;

            public CustomViewHolder(View itemView) {
                super(itemView);

                itemView.setOnClickListener(this);
                imageView = (ImageView) itemView.findViewById(R.id.imageView_thumbnail);

            }

            @Override
            public void onClick(View v) {

                int position = getAdapterPosition();

            }
        }

        public class LoadingViewHolder extends RecyclerView.ViewHolder {
            public ProgressBar progressBar;

            public LoadingViewHolder(View progressView) {
                super(progressView);
                progressBar = (ProgressBar) progressView.findViewById(R.id.progressBar);
            }
        }

    }

My previous code:

   mRecyclerViewAdapter.setOnLoadMoreListener(new IOnLoadMoreListener() {
        @Override
        public void onLoadMore() {

            listItems.add(null);

            mRecyclerViewAdapter.notifyItemChanged(listItems.size() - 1);
            mRecyclerViewAdapter.notifyDataSetChanged();

            isProgressVisible = true;

            getDataFromServer(false);

        }
    });

With my old code, I get the following IllegalStateException:

W/RecyclerView: Cannot call this method in a scroll callback. Scroll callbacks mightbe run during a measure & layout pass where you cannot change theRecyclerView data. Any method call that might change the structureof the RecyclerView or the adapter contents should be postponed tothe next frame.
java.lang.IllegalStateException: 

at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:2581)
at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeChanged(RecyclerView.java:4943)
at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyItemRangeChanged(RecyclerView.java:11373)
at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyItemRangeChanged(RecyclerView.java:11364)
at android.support.v7.widget.RecyclerView$Adapter.notifyItemChanged(RecyclerView.java:6652)
at codsiga.com.xx.ShopFragment$1.onLoadMore(ShopFragment.java:148)
at codsiga.com.xx.ShopFragment$RecyclerViewAdapter$1.onScrolled(ShopFragment.java:229)
at android.support.v7.widget.RecyclerView.dispatchOnScrolled(RecyclerView.java:4618)
at android.support.v7.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:3679)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3323)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3844)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1741)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1585)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1494)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:636)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1795)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.support.design.widget.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:131)
at android.support.design.widget.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java:42)
at android.support.design.widget.AppBarLayout$ScrollingViewBehavior.onLayoutChild(AppBarLayout.java:1391)
at android.support.design.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:870)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1079)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1741)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1585)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1494)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1741)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1585)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1494)

03-22 11:36:32.083 3189-3189/codsiga.com.xx W/RecyclerView:    
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:726)
at android.view.View.layout(View.java:17637)
at android.view.ViewGroup.layout(ViewGroup.java:5575)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2346)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2068)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6337)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:874)
at android.view.Choreographer.doCallbacks(Choreographer.java:686)
at android.view.Choreographer.doFrame(Choreographer.java:621)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:860)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

So as suggested on various StackOverflow answers, I modified it like below:

modified code looking at various answers:

mRecyclerViewAdapter.setOnLoadMoreListener(new IOnLoadMoreListener() {
            @Override
            public void onLoadMore() {

                recyclerView.post(new Runnable() {
                    @Override
                    public void run() {

                        listItems.add(null);

                        mRecyclerViewAdapter.notifyItemChanged(listItems.size() - 1);
                        mRecyclerViewAdapter.notifyDataSetChanged();

                        isProgressVisible = true;

                        getDataFromServer(false);


                    }
                });
            }
        });

Now, I don't get the error, but the onLoadMore() is called twice, everytime and the views in recyclerview keep refreshing everytime. What is it I am doing wrong? Please see the logcat output:

D/xx: Response => {…}
D/xx: Length = 9
D/xx: Query 'SELECT * FROM products' returned 9 rows

D/xx: Response => {…}
D/xx: Length = 9
D/xx: Query 'SELECT * FROM products' returned 18 rows
like image 509
Vamsi Challa Avatar asked Mar 22 '17 06:03

Vamsi Challa


2 Answers

Cannot call this method in a scroll callback. Scroll callbacks mightbe run during a measure & layout pass where you cannot change theRecyclerView data. Any method call that might change the structureof the RecyclerView or the adapter contents should be postponed tothe next frame.

The warning itself is an explaination. In other words, you are updating a view while recyclerview is calculating layout measurements.

What went wrong? The implementation below -

mRecyclerViewAdapter.notifyItemChanged(listItems.size() - 1);
mRecyclerViewAdapter.notifyDataSetChanged();

What you can do is post this UI operation to the next UI frame. You can do this by calling View.post() method.

recyclerView.post(new Runnable() {
        public void run() {
            // There is no need to use notifyDataSetChanged()
            mRecyclerViewAdapter.notifyItemInserted(allArticles.size() - 1);
        }
    });
like image 111
Paresh P. Avatar answered Nov 07 '22 19:11

Paresh P.


I use the following variant to update recyclerViewAdapter:

java:

recyclerView.post(new Runnable() {
    public void run() {
        mRecyclerViewAdapter.notifyDataSetChanged();
    }
});

kotlin:

recyclerView.post {
    mRecyclerViewAdapter.notifyDataSetChanged()
}
like image 13
V.March Avatar answered Nov 07 '22 19:11

V.March