39 votes

Impossible d'appeler notifyItemInserted() depuis RecyclerView.OnScrollListener

Récemment, j'ai mis à jour mon recyclerview-v7:23 a recyclerview-v7:24.2.0 . Mon application a une liste de défilement sans fin. Le message d'erreur pointe sur la ligne notifyItemInserted lorsque j'ajoute la vue de chargement dans RecyclerView (objet nul signifie chargement, id 0 est vide, -1 est la fin de la page) et cela fonctionne bien avant (recyclerview-v7:23) mais soudainement j'ai eu une erreur comme celle-ci et d'une certaine manière mon chargement s'affiche deux fois alors qu'il en a enlevé un, il y a un chargement qui est toujours visible en haut.

    W/RecyclerView: Cannot call this method in a scroll callback. Scroll callbacks might be run during a measure & layout pass where you cannot change the RecyclerView data. Any method call that might change the structure of the RecyclerView or the adapter contents should be postponed to the next frame.java.lang.IllegalStateException:  
 at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:2403)
     at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeInserted(RecyclerView.java:4631)
     at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyItemRangeInserted(RecyclerView.java:10469)
     at android.support.v7.widget.RecyclerView$Adapter.notifyItemInserted(RecyclerView.java:6211)
     at com.sketchproject.infogue.fragments.MessageFragment.loadMessages(MessageFragment.java:109)
     at com.sketchproject.infogue.fragments.MessageFragment.access$100(MessageFragment.java:42)
     at com.sketchproject.infogue.fragments.MessageFragment$1.onLoadMore(MessageFragment.java:87)
     at com.sketchproject.infogue.modules.EndlessRecyclerViewScrollListener.onScrolled(EndlessRecyclerViewScrollListener.java:74)

Lorsque je supprime la partie de l'erreur (ajouter le chargement), cela fonctionne à nouveau correctement. Je ne sais pas pourquoi, la nouvelle version de recyclerview empêche l'ajout de données dans l'adaptateur trop rapidement ou il y a une fonction de rappel qui se déclenche lorsque "measure & layout" de recyclerview est terminé.

private void loadArticles(final int page) {
        if (!isEndOfPage && apiArticleUrl != null) {
            if (swipeRefreshLayout == null || !swipeRefreshLayout.isRefreshing()) {
                allArticles.add(null);
                articleAdapter.notifyItemInserted(allArticles.size() - 1); // error here
            }

            JsonObjectRequest articleRequest = new JsonObjectRequest(Request.Method.GET, apiArticleUrl, null,
                    new Response.Listener<JSONObject>() {
                        @Override
                        public void onResponse(JSONObject response) {

                            try {
                                String status = response.getString("status");
                                JSONObject articles = response.getJSONObject("articles");

                                String nextUrl = articles.getString("next_page_url");
                                int currentPage = articles.getInt("current_page");
                                int lastPage = articles.getInt("last_page");
                                JSONArray data = articles.optJSONArray("data");

                                apiArticleUrl = nextUrl;

                                if (status.equals(APIBuilder.REQUEST_SUCCESS)) {
                                    if (swipeRefreshLayout == null || !swipeRefreshLayout.isRefreshing()) {
                                        allArticles.remove(allArticles.size() - 1);
                                        articleAdapter.notifyItemRemoved(allArticles.size());
                                    } else {
                                        swipeRefreshLayout.setRefreshing(false);
                                        int total = allArticles.size();
                                        for (int i = 0; i < total; i++) {
                                            allArticles.remove(0);
                                        }
                                        articleAdapter.notifyItemRangeRemoved(0, total);
                                    }

                                    List<Article> moreArticles = new ArrayList<>();

                                    if (data != null) {
                                        for (int i = 0; i < data.length(); i++) {
                                            JSONObject articleData = data.getJSONObject(i);
                                            Article article = new Article();
                                            article.setId(articleData.getInt(Article.ID));
                                            article.setSlug(articleData.getString(Article.SLUG));
                                            article.setTitle(articleData.getString(Article.TITLE));
                                            article.setFeatured(articleData.getString(Article.FEATURED_REF));
                                            article.setCategoryId(articleData.getInt(Article.CATEGORY_ID));
                                            article.setCategory(articleData.getString(Article.CATEGORY));
                                            article.setSubcategoryId(articleData.getInt(Article.SUBCATEGORY_ID));
                                            article.setSubcategory(articleData.getString(Article.SUBCATEGORY));
                                            article.setContent(articleData.getString(Article.CONTENT));
                                            article.setContentUpdate(articleData.getString(Article.CONTENT_UPDATE));
                                            article.setPublishedAt(articleData.getString(Article.PUBLISHED_AT));
                                            article.setView(articleData.getInt(Article.VIEW));
                                            article.setRating(articleData.getInt(Article.RATING_TOTAL));
                                            article.setStatus(articleData.getString(Article.STATUS));
                                            moreArticles.add(article);
                                        }
                                    }

                                    int curSize = articleAdapter.getItemCount();
                                    allArticles.addAll(moreArticles);

                                    if (allArticles.size() <= 0) {
                                        Log.i("INFOGUE/Article", "Empty on page " + page);
                                        isEndOfPage = true;
                                        Article emptyArticle = new Article(0, null, "Empty page");
                                        allArticles.add(emptyArticle);
                                    } else if (currentPage >= lastPage) {
                                        Log.i("INFOGUE/Article", "End on page " + page);
                                        isEndOfPage = true;
                                        Article endArticle = new Article(-1, null, "End of page");
                                        allArticles.add(endArticle);
                                    }

                                    articleAdapter.notifyItemRangeInserted(curSize, allArticles.size() - 1);
                                } else {
                                    Log.i("INFOGUE/Article", "Error on page " + page);
                                    Helper.toastColor(getContext(), R.string.error_unknown, R.color.color_warning_transparent);

                                    isEndOfPage = true;
                                    Article failureArticle = new Article();
                                    failureArticle.setId(-2);
                                    failureArticle.setTitle(getString(R.string.error_unknown));
                                    allArticles.add(failureArticle);
                                }
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }
                    },
                    new Response.ErrorListener() {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                            error.printStackTrace();

                            if (swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing()) {
                                swipeRefreshLayout.setRefreshing(false);
                            }

                            // remove last loading
                            allArticles.remove(allArticles.size() - 1);
                            articleAdapter.notifyItemRemoved(allArticles.size());

                            String errorMessage = getString(R.string.error_unknown);
                            NetworkResponse networkResponse = error.networkResponse;
                            if (networkResponse == null) {
                                if (error.getClass().equals(TimeoutError.class)) {
                                    errorMessage = getString(R.string.error_timeout);
                                } else if (error.getClass().equals(NoConnectionError.class)) {
                                    errorMessage = getString(R.string.error_no_connection);
                                }
                            } else {
                                if (networkResponse.statusCode == 404) {
                                    errorMessage = getString(R.string.error_not_found);
                                } else if (networkResponse.statusCode == 500) {
                                    errorMessage = getString(R.string.error_server);
                                } else if (networkResponse.statusCode == 503) {
                                    errorMessage = getString(R.string.error_maintenance);
                                }
                            }
                            Helper.toastColor(getContext(), errorMessage, R.color.color_danger_transparent);

                            // add error view holder
                            isEndOfPage = true;
                            Article errorArticle = new Article();
                            errorArticle.setId(-2);
                            errorArticle.setTitle(errorMessage);
                            allArticles.add(errorArticle);
                        }
                    }
            );

            articleRequest.setTag("articles");
            articleRequest.setRetryPolicy(new DefaultRetryPolicy(
                    APIBuilder.TIMEOUT_SHORT,
                    DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                    DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
            VolleySingleton.getInstance(getContext()).addToRequestQueue(articleRequest);
        }
    }

ce code a été appelé depuis la méthode onCreate

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

            // Set the adapter
            if (view instanceof RecyclerView) {
                Context context = view.getContext();
                recyclerView = (RecyclerView) view;

                // determine column of list
                LinearLayoutManager linearLayoutManager;
                if (mColumnCount <= 1) {
                    linearLayoutManager = new LinearLayoutManager(context);
                } else {
                    linearLayoutManager = new GridLayoutManager(context, mColumnCount);
            }

        // if article list authored by logged user then prefer editable view holder
        if (mMyArticle) {
            articleAdapter = new ArticleRecyclerViewAdapter(allArticles, mArticleListListener, mArticleEditableListener);
        } else {
            articleAdapter = new ArticleRecyclerViewAdapter(allArticles, mArticleListListener, hasHeader);
        }

        // set the adapter and attach custom scroll listener that triggered onLoadMore() and onReachTop()
        recyclerView.setAdapter(articleAdapter);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener(linearLayoutManager) {
            @Override
            public void onLoadMore(final int page, int totalItemsCount) {
                if (!isFirstCall) {
                    loadArticles(page);
                }
            }

            @Override
            public void onReachTop(boolean isFirst) {
                // activate swipe function when list reach top only, find out where do fragment attached
                if (getActivity() instanceof ArticleActivity) {
                    ((ArticleActivity) getActivity()).setSwipeEnable(isFirst);
                } else if (getActivity() instanceof ApplicationActivity) {
                    ((ApplicationActivity) getActivity()).setSwipeEnable(isFirst);
                }
            }
        });

        if (isFirstCall) {
            isFirstCall = false;
            loadArticles(0);
        }
    }
    return view;
} 

Mes questions sont les suivantes :

  1. Le problème vient-il de la nouvelle version de RecyclerView ?
  2. Est-il mauvais de mettre en œuvre notifyItemInserted dans l'écouteur de défilement ? Cela fonctionnait auparavant.
  3. Comment puis-je résoudre ce problème ?

Mise à jour de

when I logged the code inside first call and scroll,
09-12 03:49:10.078 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers URL http://192.168.43.141:8000/api/contributor/support/followers?contributor_id=1
09-12 03:49:26.421 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers first call
09-12 03:49:26.421 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers URL http://192.168.43.141:8000/api/contributor/support/followers?contributor_id=1
09-12 03:49:26.617 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers second call (scroll)
09-12 03:49:26.618 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers URL http://192.168.43.141:8000/api/contributor/support/followers?contributor_id=1
09-12 03:49:27.365 7046-7046/com.sketchproject.infogue I/Infogue/Contributor: Followers second call (scroll)

Ils sont appelés deux fois lors du premier chargement, après le premier appel et le chargement de la vue, le défilement est déclenché d'une manière ou d'une autre et ils sont appelés à nouveau.

91voto

scottyab Points 6760

Vous pouvez également poster en utilisant la vue.

   recyclerView.post(new Runnable() {
        public void run() {
            articleAdapter.notifyItemInserted(allArticles.size() - 1);
        }
    });

31voto

Sachin Saxena Points 478
  1. Le problème ne concerne pas la nouvelle version de Recyclerview.

2 & 3. Vous ne pouvez pas modifier un élément pendant qu'il est en cours de paramétrage (en appelant onBindViewHolder). Dans ce cas, vous devez appeler notifyItemInserted à la fin de la boucle actuelle en appelant Handler.post().

Handler handler = new Handler();

    final Runnable r = new Runnable() {
        public void run() {
            articleAdapter.notifyItemInserted(allArticles.size() - 1);
        }
    };

    handler.post(r);

J'espère que cela résoudra votre problème.

4voto

wonsuc Points 682

Vous pouvez également utiliser le service Google Play API Tâches

Tasks.call(new Callable<Void>() {
    @Override
    public Void call() throws Exception {
        allArticles.add(null);
        articleAdapter.notifyItemInserted(allArticles.size() - 1);
        return null;
    }
});

0voto

Tigran Babajanyan Points 638

Dans certains cas, onScrollStateChanged peut être suffisant.

override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
    super.onScrollStateChanged(recyclerView, newState)
    adapter.notifyDataSetChanged()
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X