web-dev-qa-db-fra.com

RecyclerView et Java.lang.IndexOutOfBoundsException: incohérence détectée. PositionViewHolder de l'adaptateur de titulaire de vue non valide dans les appareils Samsung

J'ai une vue recycleur qui fonctionne parfaitement sur tous les appareils, sauf Samsung. Sur Samsung, je suis

Java.lang.IndexOutOfBoundsException: incohérence détectée. Adaptateur de titulaire de vue non valide positionViewHolder

quand je reviens au fragment avec la vue recycleur d'une autre activité.

Code de l'adaptateur: 

public class FeedRecyclerAdapter extends RecyclerView.Adapter<FeedRecyclerAdapter.MovieViewHolder> {
    public static final String getUserPhoto = APIConstants.BASE_URL + APIConstants.PICTURE_PATH_SMALL;
    Movie[] mMovies = null;
    Context mContext = null;
    Activity mActivity = null;
    LinearLayoutManager mManager = null;
    private Bus uiBus = null;
    int mCountOfLikes = 0;

    //Constructor
    public FeedRecyclerAdapter(Movie[] movies, Context context, Activity activity,
                               LinearLayoutManager manager) {
        mContext = context;
        mActivity = activity;
        mMovies = movies;
        mManager = manager;
        uiBus = BusProvider.getUIBusInstance();
    }

    public void setMoviesAndNotify(Movie[] movies, boolean movieIgnored) {
        mMovies = movies;
        int firstItem = mManager.findFirstVisibleItemPosition();
        View firstItemView = mManager.findViewByPosition(firstItem);
        int topOffset = firstItemView.getTop();
        notifyDataSetChanged();
        if(movieIgnored) {
            mManager.scrollToPositionWithOffset(firstItem - 1, topOffset);
        } else {
            mManager.scrollToPositionWithOffset(firstItem, topOffset);
        }
    }

    // Create new views (called by layout manager)
    @Override
    public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.feed_one_recommended_movie_layout, parent, false);

        return new MovieViewHolder(view);
    }

    // Replaced contend of each view (called by layout manager)
    @Override
    public void onBindViewHolder(MovieViewHolder holder, int position) {
        setLikes(holder, position);
        setAddToCollection(holder, position);
        setTitle(holder, position);
        setIgnoreMovieInfo(holder, position);
        setMovieInfo(holder, position);
        setPosterAndTrailer(holder, position);
        setDescription(holder, position);
        setTags(holder, position);
    }

    // returns item count (called by layout manager)
    @Override
    public int getItemCount() {
        return mMovies != null ? mMovies.length : 0;
    }

    private void setLikes(final MovieViewHolder holder, final int position) {
        List<Reason> likes = new ArrayList<>();
        for(Reason reason : mMovies[position].reasons) {
            if(reason.title.equals("Liked this movie")) {
                likes.add(reason);
            }
        }
        mCountOfLikes = likes.size();
        holder.likeButton.setText(mContext.getString(R.string.like)
            + Html.fromHtml(getCountOfLikesString(mCountOfLikes)));
        final MovieRepo repo = MovieRepo.getInstance();
        final int pos = position;
        final MovieViewHolder viewHolder = holder;
        holder.likeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mMovies[pos].isLiked) {
                    repo.unlikeMovie(AuthStore.getInstance()
                        .getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
                        @Override
                        public void success(Movie movie, Response response) {
                            Drawable img = mContext.getResources().getDrawable(R.drawable.ic_like);
                            viewHolder.likeButton
                                .setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
                            if (--mCountOfLikes <= 0) {
                                viewHolder.likeButton.setText(mContext.getString(R.string.like));
                            } else {
                                viewHolder.likeButton
                                    .setText(Html.fromHtml(mContext.getString(R.string.like)
                                        + getCountOfLikesString(mCountOfLikes)));
                            }
                            mMovies[pos].isLiked = false;
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext.getApplicationContext(),
                                mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG)
                                .show();
                        }
                    });
                } else {
                    repo.likeMovie(AuthStore.getInstance()
                        .getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
                        @Override
                        public void success(Movie movie, Response response) {
                            Drawable img = mContext.getResources().getDrawable(R.drawable.ic_liked_green);
                            viewHolder.likeButton
                                .setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
                            viewHolder.likeButton
                                .setText(Html.fromHtml(mContext.getString(R.string.like)
                                    + getCountOfLikesString(++mCountOfLikes)));
                            mMovies[pos].isLiked = true;
                            setComments(holder, position);
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext,
                                mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }
        });
    }

    private void setComments(final MovieViewHolder holder, final int position) {
        holder.likeAndSaveButtonLayout.setVisibility(View.GONE);
        holder.commentsLayout.setVisibility(View.VISIBLE);
        holder.sendCommentButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (holder.commentsInputEdit.getText().length() > 0) {
                    CommentRepo repo = CommentRepo.getInstance();
                  repo.sendUserComment(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
                        holder.commentsInputEdit.getText().toString(), new Callback<Void>() {
                            @Override
                            public void success(Void aVoid, Response response) {
                                Toast.makeText(mContext, mContext.getString(R.string.thanks_for_your_comment),
                                    Toast.LENGTH_SHORT).show();
                                hideCommentsLayout(holder);
                            }

                            @Override
                            public void failure(RetrofitError error) {
                                Toast.makeText(mContext, mContext.getString(R.string.cannot_add_comment),
                                    Toast.LENGTH_LONG).show();
                            }
                        });
                } else {
                    hideCommentsLayout(holder);
                }
            }
        });
    }

    private void hideCommentsLayout(MovieViewHolder holder) {
        holder.commentsLayout.setVisibility(View.GONE);
        holder.likeAndSaveButtonLayout.setVisibility(View.VISIBLE);
    }

    private void setAddToCollection(final MovieViewHolder holder, int position) {
        final int pos = position;
        if(mMovies[position].isInWatchlist) {
            holder.saveButton
              .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);
        }
        final CollectionRepo repo = CollectionRepo.getInstance();
        holder.saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!mMovies[pos].isInWatchlist) {
                   repo.addMovieToCollection(AuthStore.getInstance().getAuthToken(), 0, mMovies[pos].id, new Callback<MovieCollection[]>() {
                            @Override
                            public void success(MovieCollection[] movieCollections, Response response) {
                                holder.saveButton
                                    .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);

                                mMovies[pos].isInWatchlist = true;
                            }

                            @Override
                            public void failure(RetrofitError error) {
                                Toast.makeText(mContext, mContext.getString(R.string.movie_not_added_to_collection),
                                    Toast.LENGTH_LONG).show();
                            }
                        });
                } else {
                 repo.removeMovieFromCollection(AuthStore.getInstance().getAuthToken(), 0,
                        mMovies[pos].id, new Callback<MovieCollection[]>() {
                        @Override
                        public void success(MovieCollection[] movieCollections, Response response) {
                            holder.saveButton
                                .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_plus, 0, 0, 0);

                            mMovies[pos].isInWatchlist = false;
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext,
                                mContext.getString(R.string.cannot_delete_movie_from_watchlist),
                                Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }
        });
    }

    private String getCountOfLikesString(int countOfLikes) {
        String countOfLikesStr;
        if(countOfLikes == 0) {
            countOfLikesStr = "";
        } else if(countOfLikes > 999) {
            countOfLikesStr = " " + (countOfLikes/1000) + "K";
        } else if (countOfLikes > 999999){
            countOfLikesStr = " " + (countOfLikes/1000000) + "M";
        } else {
            countOfLikesStr = " " + String.valueOf(countOfLikes);
        }
        return "<small>" + countOfLikesStr + "</small>";
    }

    private void setTitle(MovieViewHolder holder, final int position) {
        holder.movieTitleTextView.setText(mMovies[position].title);
        holder.movieTitleTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mContext, mMovies[position].id, true, false);
            }
        });
    }

    private void setIgnoreMovieInfo(MovieViewHolder holder, final int position) {
        holder.ignoreMovie.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieRepo repo = MovieRepo.getInstance();
                repo.hideMovie(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
                    new Callback<Void>() {
                        @Override
                        public void success(Void aVoid, Response response) {
                            Movie[] newMovies = new Movie[mMovies.length - 1];
                            for (int i = 0, j = 0; j < mMovies.length; i++, j++) {
                                if (i != position) {
                                    newMovies[i] = mMovies[j];
                                } else {
                                    if (++j < mMovies.length) {
                                        newMovies[i] = mMovies[j];
                                    }
                                }
                            }
                            uiBus.post(new MoviesChangedEvent(newMovies));
                            setMoviesAndNotify(newMovies, true);
                            Toast.makeText(mContext, mContext.getString(R.string.movie_ignored),
                                Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext, mContext.getString(R.string.movie_ignored_failed),
                                Toast.LENGTH_LONG).show();
                        }
                    });
            }
        });
    }

    private void setMovieInfo(MovieViewHolder holder, int position) {
        String imdp = "IMDB: ";
        String sources = "", date;
        if(mMovies[position].showtimes != null && mMovies[position].showtimes.length > 0) {
            int countOfSources = mMovies[position].showtimes.length;
            for(int i = 0; i < countOfSources; i++) {
                sources += mMovies[position].showtimes[i].name + ", ";
            }
            sources = sources.trim();
            if(sources.charAt(sources.length() - 1) == ',') {
                if(sources.length() > 1) {
                    sources = sources.substring(0, sources.length() - 2);
                } else {
                    sources = "";
                }
            }
        } else {
            sources = "";
        }
        imdp += mMovies[position].imdbRating + " | ";
        if(sources.isEmpty()) {
            date = mMovies[position].releaseYear;
        } else {
            date = mMovies[position].releaseYear + " | ";
        }

        holder.movieInfoTextView.setText(imdp + date + sources);
    }

    private void setPosterAndTrailer(final MovieViewHolder holder, final int position) {
        if (mMovies[position] != null && mMovies[position].posterPath != null
            && !mMovies[position].posterPath.isEmpty()) {
            Picasso.with(mContext)
                .load(mMovies[position].posterPath)
             .error(mContext.getResources().getDrawable(R.drawable.noposter))
                .into(holder.posterImageView);
        } else {
            holder.posterImageView.setImageResource(R.drawable.noposter);
        }
        holder.posterImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mActivity, mMovies[position].id, false, false);
            }
        });
        if(mMovies[position] != null && mMovies[position].trailerLink  != null
            && !mMovies[position].trailerLink.isEmpty()) {
            holder.playTrailer.setVisibility(View.VISIBLE);
            holder.playTrailer.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    MovieDetailActivity.openView(mActivity, mMovies[position].id, false, true);
                }
            });
        }
    }

    private void setDescription(MovieViewHolder holder, int position) {
        String text = mMovies[position].overview;
        if(text == null || text.isEmpty()) {
       holder.descriptionText.setText(mContext.getString(R.string.no_description));
        } else if(text.length() > 200) {
            text = text.substring(0, 196) + "...";
            holder.descriptionText.setText(text);
        } else {
            holder.descriptionText.setText(text);
        }
        final int pos = position;
        holder.descriptionText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mActivity, mMovies[pos].id, false, false);
            }
        });
    }

    private void setTags(MovieViewHolder holder, int position) {
        List<String> tags = Arrays.asList(mMovies[position].tags);
        if(tags.size() > 0) {
            CastAndTagsFeedAdapter adapter = new CastAndTagsFeedAdapter(tags,
                mContext, ((FragmentActivity) mActivity).getSupportFragmentManager());
            holder.tags.setItemMargin(10);
            holder.tags.setAdapter(adapter);
        } else {
            holder.tags.setVisibility(View.GONE);
        }
    }

    // class view holder that provide us a link for each element of list
    public static class MovieViewHolder extends RecyclerView.ViewHolder {
        TextView movieTitleTextView, movieInfoTextView, descriptionText, reasonsCountText;
        TextView reasonText1, reasonAuthor1, reasonText2, reasonAuthor2;
        EditText commentsInputEdit;
        Button likeButton, saveButton, playTrailer, sendCommentButton;
        ImageButton ignoreMovie;
        ImageView posterImageView, userPicture1, userPicture2;
        TwoWayView tags;
        RelativeLayout mainReasonsLayout, firstReasonLayout, secondReasonLayout, reasonsListLayout;
        RelativeLayout commentsLayout;
        LinearLayout likeAndSaveButtonLayout;
        ProgressBar progressBar;

        public MovieViewHolder(View view) {
            super(view);
            movieTitleTextView = (TextView)view.findViewById(R.id.movie_title_text);
            movieInfoTextView = (TextView)view.findViewById(R.id.movie_info_text);
            descriptionText = (TextView)view.findViewById(R.id.text_description);
            reasonsCountText = (TextView)view.findViewById(R.id.reason_count);
            reasonText1 = (TextView)view.findViewById(R.id.reason_text_1);
            reasonAuthor1 = (TextView)view.findViewById(R.id.author_1);
            reasonText2 = (TextView)view.findViewById(R.id.reason_text_2);
            reasonAuthor2 = (TextView)view.findViewById(R.id.author_2);
            commentsInputEdit = (EditText)view.findViewById(R.id.comment_input);
            likeButton = (Button)view.findViewById(R.id.like_button);
            saveButton = (Button)view.findViewById(R.id.save_button);
            playTrailer = (Button)view.findViewById(R.id.play_trailer_button);
            sendCommentButton = (Button)view.findViewById(R.id.send_button);
            ignoreMovie = (ImageButton)view.findViewById(R.id.ignore_movie_imagebutton);
            posterImageView = (ImageView)view.findViewById(R.id.poster_image);
            userPicture1 = (ImageView)view.findViewById(R.id.user_picture_1);
            userPicture2 = (ImageView)view.findViewById(R.id.user_picture_2);
            tags = (TwoWayView)view.findViewById(R.id.list_view_feed_tags);
            mainReasonsLayout = (RelativeLayout)view.findViewById(R.id.reasons_main_layout);
            firstReasonLayout = (RelativeLayout)view.findViewById(R.id.first_reason);
            secondReasonLayout = (RelativeLayout)view.findViewById(R.id.second_reason);
            reasonsListLayout = (RelativeLayout)view.findViewById(R.id.reasons_list);
            commentsLayout = (RelativeLayout)view.findViewById(R.id.comments_layout);
            likeAndSaveButtonLayout = (LinearLayout)view
                .findViewById(R.id.like_and_save_buttons_layout);
            progressBar = (ProgressBar)view.findViewById(R.id.centered_progress_bar);
        }
    }
}

Exception:

Java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{42319ed8 position=1 id=-1, oldPos=0, pLpos:0 scrap tmpDetached no parent}
 at Android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.Java:4166)
 at Android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.Java:4297)
 at Android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.Java:4278)
 at Android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.Java:1947)
 at Android.support.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.Java:434)
 at Android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.Java:1322)
 at Android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.Java:556)
 at Android.support.v7.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.Java:171)
 at Android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.Java:2627)
 at Android.support.v7.widget.RecyclerView.onLayout(RecyclerView.Java:2971)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.Java:562)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.widget.FrameLayout.layoutChildren(FrameLayout.Java:453)
 at Android.widget.FrameLayout.onLayout(FrameLayout.Java:388)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.support.v4.view.ViewPager.onLayout(ViewPager.Java:1626)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1677)
 at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:1531)
 at Android.widget.LinearLayout.onLayout(LinearLayout.Java:1440)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.support.v4.view.ViewPager.onLayout(ViewPager.Java:1626)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1677)
 at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:1531)
 at Android.widget.LinearLayout.onLayout(LinearLayout.Java:1440)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.widget.FrameLayout.layoutChildren(FrameLayout.Java:453)
 at Android.widget.FrameLayout.onLayout(FrameLayout.Java:388)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1677)
 at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:1531)
 at Android.widget.LinearLayout.onLayout(LinearLayout.Java:1440)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.widget.FrameLayout.layoutChildren(FrameLayout.Java:453)
 at Android.widget.FrameLayout.onLayout(FrameLayout.Java:388)
07-30 12:48:22.688    9590-9590/com.Filmgrail.Android.debug W/System.err? at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1677)
 at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:1531)
 at Android.widget.LinearLayout.onLayout(LinearLayout.Java:1440)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.widget.FrameLayout.layoutChildren(FrameLayout.Java:453)
 at Android.widget.FrameLayout.onLayout(FrameLayout.Java:388)
 at Android.view.View.layout(View.Java:15746)
 at Android.view.ViewGroup.layout(ViewGroup.Java:4867)
 at Android.view.ViewRootImpl.performLayout(ViewRootImpl.Java:2356)
 at Android.view.ViewRootImpl.performTraversals(ViewRootImpl.Java:2069)
 at Android.view.ViewRootImpl.doTraversal(ViewRootImpl.Java:1254)
 at Android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.Java:6630)
 at Android.view.Choreographer$CallbackRecord.run(Choreographer.Java:803)
 at Android.view.Choreographer.doCallbacks(Choreographer.Java:603)
 at Android.view.Choreographer.doFrame(Choreographer.Java:573)
 at Android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.Java:789)
 at Android.os.Handler.handleCallback(Handler.Java:733)
 at Android.os.Handler.dispatchMessage(Handler.Java:95)
 at Android.os.Looper.loop(Looper.Java:136)
 at Android.app.ActivityThread.main(ActivityThread.Java:5479)
 at Java.lang.reflect.Method.invokeNative(Native Method)
 at Java.lang.reflect.Method.invoke(Method.Java:515)
 at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:1283)
 at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:1099)
 at dalvik.system.NativeStart.main(Native Method)

Comment puis-je réparer cela?

Ce problème est dû à RecyclerView Données modifiées dans un autre thread. Le meilleur moyen est de vérifier tous les accès aux données. Et une solution de contournement englobe LinearLayoutManager.

Réponse précédente

En fait, RecyclerView contenait un bogue et le support 23.1.1 n’était toujours pas corrigé. 

Pour une solution de contournement, notez que les piles de trace arrière, si nous pouvons attraper ce Exception dans une classe de quelque classe, il peut ignorer cet accident. Pour moi, je crée un LinearLayoutManagerWrapper et remplace le onLayoutChildren:

public class WrapContentLinearLayoutManager extends LinearLayoutManager {
    //... constructor
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            Log.e("TAG", "meet a IOOBE in RecyclerView");
        }
    }
}

Puis définissez-le sur RecyclerView:

RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);

recyclerView.setLayoutManager(new WrapContentLinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));

En fait attraper cette exception, et semble pas encore aucun effet secondaire. 

De même, si vous utilisez GridLayoutManager ou StaggeredGridLayoutManager, vous devez créer un wrapper pour celui-ci.

Avis: Le RecyclerView peut être dans un mauvais état interne.

149
sakiM

Ceci est un exemple d'actualisation de données avec un contenu entièrement nouveau . Vous pouvez facilement le modifier pour l'adapter à vos besoins .

notifyItemRangeRemoved(0, previousContentSize);

avant:

notifyItemRangeInserted(0, newContentSize);

Ceci est la solution correcte et est également mentionné dans ce message par un membre du projet PSBA.

56
box

Nouvelle réponse: Utilisez DiffUtil pour toutes les mises à jour de RecyclerView. Cela aidera avec les performances et le bogue ci-dessus. Vois ici

Réponse précédente: Cela a fonctionné pour moi. La clé est de ne pas utiliser notifyDataSetChanged() et de faire les bonnes choses dans le bon ordre:

public void setItems(ArrayList<Article> newArticles) {
    //get the current items
    int currentSize = articles.size();
    //remove the current items
    articles.clear();
    //add all the new items
    articles.addAll(newArticles);
    //tell the recycler view that all the old items are gone
    notifyItemRangeRemoved(0, currentSize);
    //tell the recycler view how many new items we added
    notifyItemRangeInserted(0, newArticles.size());
}
17
Bolling

J'ai rencontré ce problème une fois et je l'ai résolu en encapsulant la variable LayoutManager et en désactivant les animations prédictives.

Voici un exemple:

public class LinearLayoutManagerWrapper extends LinearLayoutManager {

  public LinearLayoutManagerWrapper(Context context) {
    super(context);
  }

  public LinearLayoutManagerWrapper(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
  }

  public LinearLayoutManagerWrapper(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }

  @Override
  public boolean supportsPredictiveItemAnimations() {
    return false;
  }
}

Et réglez-le sur RecyclerView:

RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManagerWrapper(context, LinearLayoutManager.VERTICAL, false);
15
hcknl

J'avais un problème similaire.

Problème dans le code d'erreur ci-dessous:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize - 1, messageListHistory.size() -1);

Solution:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize, messageListHistory.size() -prevSize);
14
Vandai Doan

Selon ce problème , le problème a été résolu et a probablement été publié vers le début de l'année 2015. Citation tirée du même fil de discussion :

Il est spécifiquement lié à l'appel de notifyDataSetChanged. [...]

Au fait, je conseille vivement de ne pas utiliser notifyDataSetChanged car il tue les animations et les performances. Dans ce cas également, l’utilisation d’événements de notification spécifiques contournera le problème.

Si vous rencontrez toujours des problèmes avec une version récente de la bibliothèque de support, je vous conseillerais de passer vos appels à notifyXXX (et plus précisément à votre utilisation de notifyDataSetChanged) à l'intérieur de votre adaptateur, afin de vous assurer que vous respectez le RecyclerView.Adapter Contrat. Veillez également à émettre ces notifications sur le fil principal.

13
stkent

J'ai eu le même problème. Cela était dû au retard de la notification de l'adaptateur concernant l'insertion d'élément.

Mais ViewHolder a essayé de redessiner certaines données dans sa vue et a lancé le RecyclerView mesure et racontant le compte des enfants - à ce moment, il s'est écrasé (la liste d'éléments et sa taille étaient déjà mises à jour, mais l'adaptateur n'a pas encore été notifié).

10
porfirion

une autre raison pour laquelle ce problème se produit est lorsque vous appelez ces méthodes avec des index incorrects (les index qu'il n'y a PAS eu, les insérez ou les supprimez)

-notifyItemRangeRemoved 

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

vérifiez les paramètres d'indexation de ces méthodes et assurez-vous qu'elles sont précises et correctes.

7
Amir Ziarati

Cela se produit lorsque vous spécifiez une position incorrecte pour notifyItemChanged, NotifyItemRangeInserted etc. Pour moi:

Avant: (erroné)

public void addData(List<ChannelItem> list) {
  int initialSize = list.size();
  mChannelItemList.addAll(list);
  notifyItemRangeChanged(initialSize - 1, mChannelItemList.size());
 } 

Après: (correct)

 public void addData(List<ChannelItem> list) {
  int initialSize = mChannelItemList.size();
  mChannelItemList.addAll(list);
  notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); //Correct position 
 }
6
Saurabh Padwekar

Ce bogue n'est toujours pas corrigé dans 23.1.1, mais une solution de contournement courante consisterait à intercepter l'exception.

6
Farooq AR

Ce problème est dû à RecyclerView Data modifié dans différent fil

Peut confirmer que le threading est un problème et que depuis que j'ai rencontré le problème et que RxJava est de plus en plus populaire: assurez-vous d'utiliser .observeOn(AndroidSchedulers.mainThread()) à chaque fois que vous appelez notify[whatever changed] 

exemple de code de l'adaptateur:

myAuxDataStructure.getChangeObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<AuxDataStructure>() {

    [...]

    @Override
    public void onNext(AuxDataStructure o) {
        [notify here]
    }
});
4
Philipp

Dans mon cas, chaque fois que j'appelle notifyItemRemoved (0), il s'est écrasé. En fait, j’ai mis setHasStableIds(true) et dans getItemId je viens de retourner la position de l’article. J'ai fini par le mettre à jour pour renvoyer la hashCode() de l'élément ou son identifiant unique défini par l'utilisateur, ce qui a résolu le problème.

4
Arst

Dans mon cas, je changeais les données précédemment dans un thread avec mRecyclerView.post (nouveau Runnable ...), puis à nouveau plus tard, les données dans le thread de l'interface utilisateur, ce qui provoquait des incohérences. 

3
Niroj Shr

Un problème ne m'est apparu que lorsque:

J'ai créé l'adaptateur avec une liste liste vide . Puis j'ai inséré des éléments et appelé notifyItemRangeInserted.

Solution:

J'ai résolu ce problème en ne créant l'adaptateur qu'après avoir reçu le premier bloc de données et l'avoir initialisé immédiatement. Le bloc suivant peut ensuite être inséré et notifyItemRangeInserted appelé sans problème.

3
Willi Mentzel

Dans mon cas, je rencontrais ce problème à cause de la mise à jour des données du serveur (j'utilise Firebase Firestore). Alors que le premier jeu de données est traité par DiffUtil en arrière-plan, un autre jeu de mises à jour arrive et provoque un problème de simultanéité en démarrant un autre DiffUtil. 

En bref, si vous utilisez DiffUtil sur un thread d'arrière-plan qui revient ensuite au thread principal pour envoyer les résultats au RecylerView, vous courez alors le risque d'obtenir cette erreur lorsque plusieurs mises à jour de données arrivent rapidement.

J'ai résolu ce problème en suivant les conseils de cette merveilleuse explication: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2

Juste pour expliquer la solution, il faut envoyer les mises à jour pendant que la dernière est en cours d'exécution sur un Deque. Le deque peut ensuite exécuter les mises à jour en attente une fois que celle en cours est terminée, ce qui permet de gérer toutes les mises à jour ultérieures, tout en évitant les erreurs d'incohérence!

J'espère que cela aide parce que celui-ci m'a fait me gratter la tête!

3
dejavu89

Mon problème était que, même si j'effaçais la liste de tableaux contenant le modèle de données pour la vue recycleur, je n'avais pas notifié l'adaptateur de cette modification. Il contenait donc des données obsolètes du modèle précédent. Ce qui a provoqué la confusion sur la position du détenteur de la vue. Pour résoudre ce problème, informez toujours l'adaptateur que le jeu de données a été modifié avant la nouvelle mise à jour.

2
Remario

Dans mon cas, le problème était que j'ai utilisé notifyDataSetChanged lorsque la quantité de données nouvellement chargées était inférieure aux données initiales . Cette approche m'a aidé à:

adapter.notifyItemRangeChanged(0, newAmountOfData + 1);
adapter.notifyItemRangeRemoved(newAmountOfData + 1, previousAmountOfData);
2
pretty_fennec

L'erreur peut être due au fait que vos modifications sont incompatibles avec ce que vous notifiez. Dans mon cas:

myList.set(position, newItem);
notifyItemInserted(position);

Ce que je devais bien sûr faire:

myList.add(position, newItem);
notifyItemInserted(position);
2
Cristan

Les raisons sont à l'origine de ce problème:

  1. Un problème interne dans Recycler lorsque les animations d'éléments sont activées
  2. Modification sur les données du recycleur dans un autre thread
  3. Appeler les méthodes de notification de manière incorrecte

SOLUTION:

----------------- SOLUTION 1 ---------------

  • Récupérer l'exception (Non recommandé en particulier pour la raison n ° 3)

Créez un LinearLayoutManager personnalisé comme suit et définissez-le sur ReyclerView

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

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

                try {

                    super.onLayoutChildren(recycler, state);

                } catch (IndexOutOfBoundsException e) {

                    Log.e(TAG, "Inconsistency detected");
                }

            }
        }

Puis définissez RecyclerVIew Layout Manager comme suit:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUTION 2 ---------------

  • Désactiver les animations d'élément (corrige le problème s'il entraînait la raison n ° 1):

Encore une fois, créez un gestionnaire de disposition linéaire personnalisé comme suit:

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

             @Override
             public boolean supportsPredictiveItemAnimations() {
                 return false;
             }
        }

Puis définissez RecyclerVIew Layout Manager comme suit:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUTION 3 ---------------

  • Cette solution corrige le problème s'il était causé par la raison n ° 3. Vous devez vous assurer que vous utilisez les méthodes de notification de manière correcte. Vous pouvez également utiliser DiffUtil pour gérer le changement de manière intelligente, simple et fluide.Utilisation de DiffUtil dans Android RecyclerView

----------------- SOLUTION 4 ---------------

  • Pour la raison n ° 2, vous devez vérifier tous les accès aux données de la liste des recycleurs et vous assurer qu'il n'y a aucune modification sur un autre thread.
1
Islam Assi

J'ai rencontré le même problème.

Mon application utilise des composants de navigation avec un fragment contenant mon recyclerView. Ma liste s’affiche correctement la première fois que le fragment a été chargé ... mais lorsqu’il est parti et rentré, cette erreur s’est produite.

Lors de la navigation, le cycle de vie des fragments ne passait que par onDestroyView et à son retour, il commençait par onCreateView. Cependant, mon adaptateur a été initialisé dans onCreate du fragment et ne s'est pas réinitialisé lors du retour.

Le correctif consistait à initialiser l'adaptateur dans onCreateView.

J'espère que cela peut aider quelqu'un.

1
Loren

J'ai eu cette erreur parce que j'appelais "notifyItemInserted" deux fois par erreur.

0
Feuby

Dans mon cas, la liste contient plus de 5 000 éléments . Mon problème était que, lors du défilement de la vue du recycleur, parfois, la méthode "onBindViewHolder" était appelée alors que la méthode "myCustomAddItems" modifiait la liste.

Ma solution consistait à ajouter "synchronized (syncObject) {}" à toutes les méthodes modifiant la liste de données . Ainsi, à tout moment et à tout moment, une seule méthode peut lire cette liste.

0
user3193413

J'ai le même problème et j'ai lu que cela ne concernait que les téléphones Samsung ... Mais la réalité a montré que cela se produit dans de nombreuses marques.

Après les tests, je me suis rendu compte que cela ne se produit que lorsque vous faites défiler rapidement le RecyclerView, puis vous revenez en arrière avec le bouton Précédent ou le bouton Haut. Alors j'ai mis à l'intérieur du bouton Up et onBackpressed l'extrait ci-dessous:

someList = new ArrayList<>();
mainRecyclerViewAdapter = new MainRecyclerViewAdapter(this, someList, this);
recyclerViewMain.setAdapter(mainRecyclerViewAdapter);
finish();

Avec cette solution, il vous suffit de charger un nouvel Arraylist dans l’adaptateur et un nouvel adaptateur dans recyclerView, puis vous terminez l’activité.

J'espère que ça aide quelqu'un

0
Farmaker

J'ai eu cette erreur parce que j'appelais par erreur une méthode pour supprimer plusieurs fois une ligne spécifique de mon aperçu de recyclage. J'ai eu une méthode comme:

void removeFriends() {
    final int loc = data.indexOf(friendsView);
    data.remove(friendsView);
    notifyItemRemoved(loc);
}

J'appelais accidentellement cette méthode trois fois au lieu d'une fois, donc la deuxième fois loc était -1 et l'erreur a été donnée quand il a essayé de la supprimer. Les deux correctifs consistaient à s'assurer que la méthode n'était appelée qu'une fois et à ajouter un contrôle de cohérence comme celui-ci:

void removeFriends() {
    final int loc = data.indexOf(friendsView);
    if (loc > -1) {
        data.remove(friendsView);
        notifyItemRemoved(loc);
    }
}
0
elliptic1

Dans mon cas, les données de l'adaptateur ont changé. Et j’ai mal utilisé notifyItemInserted () pour ces modifications. Lorsque j'utilise notifyItemChanged, l'erreur a disparu.

0
oiyio