RecyclerView et java.lang.IndexOutOfBoundsException: incohérence détectée. Position de l'adaptateur de support de vue non valide ViewHolder dans les appareils Samsung


253

J'ai une vue de recycleur qui fonctionne parfaitement sur tous les appareils sauf Samsung. Sur Samsung, je reçois

java.lang.IndexOutOfBoundsException: incohérence détectée. Position de l'adaptateur du support de vue non valide

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

Code 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?


à votre retour, vos données sont-elles les mêmes que lorsque vous quittez la page?
khusrav

je suis gatting même problème comment u résoudre ....
Ashvin solanki

@ Владимир Avez-vous trouvé la réponse définitive?
Alireza Noorali

Dans mon cas, c'est parce que j'ai commencé la tâche asynchrone, et lorsque l'un d'eux se termine avant un autre et que l'utilisateur défile vers le bas et en attendant, un autre utilisateur termine et met à jour l'adaptateur peut obtenir une telle exception car la deuxième tâche a renvoyé moins de données
Vasif

Réponses:


195

Ce problème est dû aux RecyclerViewdonnées modifiées dans un thread différent. La meilleure façon est de vérifier tous les accès aux données. Et une solution de contournement se termineLinearLayoutManager .

Réponse précédente

Il y avait en fait un bogue dans RecyclerView et le support 23.1.1 n'était toujours pas corrigé.

Pour une solution de contournement, notez que les piles de backtrace, si nous pouvons attraper ce Exception dans une classe, cela peut sauter ce plantage. Pour moi, je crée un LinearLayoutManagerWrapperet 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");
        }
    }
}

Réglez-le ensuite sur RecyclerView:

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

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

En fait, saisissez cette exception et ne semble pas encore avoir d'effet secondaire.

De plus, si vous utilisez GridLayoutManager ouStaggeredGridLayoutManager vous devez créer un wrapper pour cela.

Remarque: le RecyclerViewpeut être dans un mauvais état interne.


1
où exactement mettez-vous cela? sur l'adaptateur ou l'activité?
Steve Kamau

étendre un LinearLayoutManageret remplacer cela. Je ferai un ajout dans ma réponse.
sakiM

14
code.google.com/p/android/issues/detail?id=158046 réponse # 12 a dit de ne pas faire cela.
Robert

ummm, vous avez raison. Il semble difficile de désamorcer toutes les modifications potentielles non liées à l'interface utilisateur dans mon application, je ne garderai cela que comme solution de contournement.
sakiM

1
Pour mon cas je fais sur le même fil. mDataHolder.get (). removeAll (mHiddenGenre); mAdapter.notifyItemRangeRemoved (mExpandButtonPosition, mHiddenGenre.size ());
JehandadK

73

Ceci est un exemple d'actualisation de données avec un contenu complètement nouveau. Vous pouvez facilement le modifier pour l'adapter à vos besoins. J'ai résolu cela dans mon cas en appelant:

notifyItemRangeRemoved(0, previousContentSize);

avant:

notifyItemRangeInserted(0, newContentSize);

C'est la bonne solution et est également mentionnée dans cet article par un membre du projet AOSP.


2
Cette solution fonctionne pour moi J'ai essayé beaucoup de réponses ici mais elles ne fonctionnent pas (je n'ai pas testé la première solution)
AndroLife

Le problème est que l'utilisation de ces méthodes crée cette incohérence, même lorsqu'elle est effectuée sur le même thread.
JehandadK

Je n'utilise pas notifyItemRangeInsertedet n'ai pas ce problème avec certains appareils Samsung
user25

Et tout à fait hors sujet ici. L'auteur n'a pas utilisénotifyItemRangeInserted
user25

1
Je vous remercie! Cela m'a aidé.
DmitryKanunnikoff

35

J'ai fait face à ce problème une fois, et je l'ai résolu en enveloppant le LayoutManager animations prédictives et en les désactivant.

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);

cela semble fonctionner pour moi, mais pouvez-vous dire pourquoi cela fonctionne?
Dennis Anderson

Corrigé pour moi aussi. Comment vous avez prédit que cela pourrait être la cause de cet accident.
Rahul Rastogi

1
La classe de base de la méthode LinearLayoutManager prend en charge PredictiveAnimations () renvoie false par défaut. Qu'obtenons-nous en remplaçant la méthode ici? public boolean supportsPredictiveItemAnimations() { return false; }
M. Hig

1
@ M.Hig La documentation pour LinearLayoutManagerindique que la valeur par défaut est false, mais cette déclaration est fausse :-( Le code décompilé pour LinearLayoutManagera ceci: public boolean supportsPredictiveItemAnimations () {return this.mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd ;}
Clyde

J'utilise diff utils pour mettre à jour mon adaptateur de vue recycleur et cette réponse a corrigé un plantage. Merci beaucoup, cher auteur!
Eugene P.

29

Nouvelle réponse: utilisez DiffUtil pour toutes les mises à jour de RecyclerView. Cela contribuera à la fois aux performances et au 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());
}

1
C'est la solution la plus complète avec une bonne explication. Merci!
Sakiboy

alors quel est le but d'utiliser la plage de notification insérée au lieu de notifydatasetchanged (), @Bolling.
Ankur_009

@FilipLuch Pouvez-vous expliquer pourquoi?
Sreekanth Karumanaghat

3
@SreekanthKarumanaghat bien sûr, je ne sais pas pourquoi je n'ai pas expliqué la raison. Fondamentalement, il efface puis recrée tous les éléments de la liste. Comme dans les résultats de recherche, très souvent, vous obtenez les mêmes éléments, ou lorsque l'actualisation est effectuée, vous obtenez les mêmes éléments et vous finissez par tout recréer, ce qui est une perte de performances. Utilisez plutôt DiffUtils et ne mettez à jour que les modifications plutôt que tous les éléments. C'est comme passer de A à Z à chaque fois, mais vous n'avez changé que le F là-dedans.
Filip Luchianenco

2
DiffUtil est un trésor caché. Merci d'avoir partagé!
Sileria

22

Les raisons ont provoqué ce problème:

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

SOLUTION:

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

  • Attraper 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");
                }

            }
        }

Ensuite, définissez RecyclerVIew Layout Manager comme suit:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

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

  • Désactiver les animations d'objets (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;
             }
        }

Ensuite, définissez RecyclerVIew Layout Manager comme suit:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

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

  • Cette solution résout le problème s'il est causé par la raison n ° 3. Vous devez vous assurer que vous utilisez correctement les méthodes de notification. Vous pouvez également utiliser DiffUtil pour gérer le changement de manière intelligente, facile 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 à la liste des recycleurs et vous assurer qu'il n'y a pas de modification sur un autre thread.

cela a fonctionné dans mon scénario, je ne peux pas utiliser DiffUtil parce que j'ai des composants personnalisés pour les recycleurs et les adaptateurs, et l'erreur se produit exactement dans des scénarios spécifiques qui sont connus, je devais juste le corriger sans recourir à la suppression des animateurs d'élément, donc je viens enveloppé dans un try & catch
RJFares

17

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);

Cela a très bien fonctionné pour moi! Je ne sais pas pourquoi nous ne pouvons pas simplement l'utiliser newList.size() - 1.
waseefakhtar

15

Selon ce problème , le problème a été résolu et a probablement été publié quelque temps vers le début de 2015. Une citation de ce même fil :

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

Btw, je conseille fortement de ne pas utiliser notifyDataSetChanged car cela 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 suggère de revoir vos appels à notifyXXX(spécifiquement, votre utilisation de notifyDataSetChanged) à l'intérieur de votre adaptateur, pour vous assurer que vous respectez le RecyclerView.Adaptercontrat (quelque peu délicat / obscur) . Assurez-vous également d'émettre ces notifications sur le fil principal.


16
pas vraiment, je suis d'accord avec votre part sur les performances, mais notifyDataSetChanged () ne tue pas les animations, pour animer à l'aide de notifyDataSetChanged (), a) appelez setHasStableIds (true) sur votre objet RecyclerView.Adapter et b) remplacez getItemId dans votre adaptateur pour renvoyer un valeur longue unique pour chaque ligne et vérifiez-les, les animations fonctionnent
PirateApp

@PirateApp Vous devriez envisager de faire votre commentaire comme une réponse. Je l'ai essayé et ça marche bien.
mr5

Pas vrai! Obtenez toujours des rapports de la console Google sur ce problème. Et l'appareil est bien sûr Samsung -Samsung Galaxy J3(2017) (j3y17lte), Android 8.0
user25

10

J'ai eu le même problème. Cela est dû au fait que j'ai retardé la notification de l'adaptateur à propos de l'insertion d'élément.

Mais a ViewHolderessayé de redessiner certaines données dans sa vue et il a commencé à RecyclerViewmesurer et à recompter le nombre d'enfants - à ce moment, il s'est écrasé (la liste des articles et sa taille ont déjà été mises à jour, mais l'adaptateur n'a pas encore été informé).


8

Cela se produit lorsque vous spécifiez la position incorrecte pour notifyItemChanged, notifyItemRangeInserred, 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 
 }

1
Pourquoi notifyItemRangeInserted(initialSize, mChannelItemList.size()-1);et non notifyItemRangeInserted(initialSize, list.size());?
CoolMind

Undestood. Vous avez confondu initialSizeet la listtaille. Donc, vos deux variantes sont fausses.
CoolMind

Pour moi, cela fonctionne, notifyItemRangeInserted(initialSize, list.size()-1);mais je ne comprends pas. Pourquoi dois-je réduire la taille insérée d'une unité pour itemCount?
plexus

7

une autre raison pour laquelle ce problème se produit est lorsque vous appelez ces méthodes avec des index incorrects (index qui ne se sont PAS produits, insérez-les ou supprimez-les)

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserred

-notifyItemInserred

vérifiez les paramètres d'indexation de ces méthodes et assurez-vous qu'ils sont précis et corrects.


2
C'était mon problème. Une exception se produit lors de l'ajout de rien dans la liste.
Rasel

6

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


18
Attrapez-le où, exactement? Le seul code dans la trace de la pile est le code Android natif.
howettl

1
Attrapez-le comme la réponse @saki_M.
Renan Bandeira

Est-ce que cela fonctionne réellement pour vous grâce à Renan? Avez-vous testé le correctif pendant un certain temps? Le bug ne se produit que de temps en temps, je ne verrai donc que si cela fonctionne au fil du temps.
Simon

Cela fonctionne réellement, mais certaines opinions d'enfants dans la mienne sont incohérentes.
david

@david reste incohérent quelle est la conséquence de cela?
Sreekanth Karumanaghat

4

Ce problème est dû aux données RecyclerView modifiées dans un thread différent

Peut confirmer le filetage comme un problème et depuis que j'ai rencontré le problème et que RxJava devient de plus en plus populaire: assurez-vous que vous utilisez .observeOn(AndroidSchedulers.mainThread())chaque fois que vous appeleznotify[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]
    }
});

Je suis sur le thread principal lors de l'appel à DiffUtil.calculateDiff (diffUtilForecastItemChangesAnlayser (this.mWeatherForecatsItemWithMainAndWeathers, weatherForecastItems)). DispatchUpdatesTo (this); le journal est clair sur Thread: Thread [main, 5, main]
Mathias Seguy Android2ee

4

Dans mon cas, chaque fois que j'appelle notifyItemRemoved (0), il se bloquait. Il s'est avéré que j'ai défini setHasStableIds(true)et getItemIdje viens de retourner la position de l'article. J'ai fini par le mettre à jour pour renvoyer l' hashCode()ID unique de l'article ou défini par l'utilisateur, ce qui a résolu le problème.


4

Dans mon cas, j'obtenais ce problème en raison de l'obtention de mises à jour de données du serveur (j'utilise Firebase Firestore) et tandis que le premier ensemble de données est traité par DiffUtil en arrière-plan, un autre ensemble de mise à jour de données vient et provoque un problème de concurrence en lançant 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 la chance d'obtenir cette erreur lorsque plusieurs mises à jour de données arrivent en peu de temps.

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

Pour expliquer la solution, il suffit de pousser les mises à jour pendant que la version actuelle s'exécute sur un Deque. Le deque peut ensuite exécuter les mises à jour en attente une fois la mise à jour terminée, gérant ainsi toutes les mises à jour suivantes mais en évitant également les erreurs d'incohérence!

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


Merci pour le lien!
CoolMind

3

Le problème ne s'est produit pour moi que lorsque:

J'ai créé l'adaptateur avec un liste vide . Ensuite, j'ai inséré des éléments et appelé notifyItemRangeInserted.

Solution:

J'ai résolu ce problème en créant l'adaptateur uniquement après avoir le premier bloc de données et en l'initialisant immédiatement. Le morceau suivant pourrait alors être inséré et notifyItemRangeInsertedappelé sans problème.


Je ne pense pas que ce soit la raison. J'ai de nombreux adaptateurs avec des listes vides, puis j'ai ajouté des éléments avec notifyItemRangeInserted, mais je n'ai jamais eu cette exception là-bas.
CoolMind

3

Mon problème était que même si j'effaçais à la fois la liste des tableaux contenant le modèle de données pour la vue du recycleur, je n'avais pas notifié l'adaptateur de ce changement, donc il avait des données périmées du modèle précédent. Ce qui a provoqué la confusion sur la position du support de vue. Pour résoudre ce problème, informez toujours l'adaptateur que l'ensemble de données a été modifié avant de le mettre à jour à nouveau.


ou
notifiez

mon modèle utilise la référence du conteneur c'est pourquoi
Remario

3

Dans mon cas, je modifiais les données précédemment dans un thread avec mRecyclerView.post (nouveau Runnable ...), puis à nouveau plus tard, des données modifiées dans le thread d'interface utilisateur, ce qui a provoqué une incohérence.


1
j'ai la même situation que toi, comment l'as-tu résolu? merci
baderkhane

2

L'erreur peut être due au fait que vos modifications ne correspondent pas à 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

Dans mon cas, le problème était que j'utilisais 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);

Pourquoi notifyDataSetChangeddépend de nouvelles données? Je pensais que cela rafraîchirait toute la liste.
CoolMind

2

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

Mon application utilise des composants de navigation avec un fragment contenant ma recyclerView. Ma liste s'est bien affichée la première fois que le fragment a été chargé ... mais lors de la navigation et du retour, cette erreur s'est produite.

Lorsque vous vous éloignez, le cycle de vie du fragment ne passe que par onDestroyView et lors du retour, il démarre à onCreateView. Cependant, mon adaptateur a été initialisé dans le 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.


0

J'ai eu cette erreur parce que j'appelais par erreur une méthode pour supprimer plusieurs fois une ligne spécifique de ma vue de recyclage. J'avais 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 lors de la tentative de suppression. Les deux correctifs devaient garantir que la méthode n'était appelée qu'une seule fois, et également ajouter un contrôle d'intégrité comme celui-ci:

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

0

J'ai eu le même problème et j'ai lu que cela ne s'était produit que sur 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 que vous revenez soit avec le bouton de retour soit avec le bouton Haut. J'ai donc mis à l'intérieur du bouton Haut et on a appuyé sur l'extrait ci-dessous:

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

Avec cette solution, vous chargez simplement un nouvel Arraylist dans l'adaptateur et un nouvel adaptateur dans recyclerView, puis vous terminez l'activité.

J'espère que cela aide quelqu'un


0

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


0

Dans mon cas, j'ai eu plus de 5000 éléments dans la liste. Mon problème était que lors du défilement de la vue du recycleur, parfois la méthode «onBindViewHolder» était appelée tandis que la méthode «myCustomAddItems» modifiait la liste.

Ma solution a été d'ajouter "synchronized (syncObject) {}" à toutes les méthodes qui modifient la liste des données. De cette façon, à tout moment, une seule méthode peut lire cette liste.


0

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


0

J'ai rencontré le même problème lorsque j'ai supprimé et mis à jour des éléments de la liste ... Après des jours d'enquête, je pense avoir finalement trouvé une solution.

Ce que vous devez faire, c'est d'abord faire toute notifyItemChangedvotre liste et ensuite seulement faire tout notifyItemRemoved dans un ordre décroissant

J'espère que cela aidera les gens qui rencontrent le même problème ...


0

J'utilise un curseur donc je ne peux pas utiliser les DiffUtils comme proposé dans les réponses populaires. Afin de le faire fonctionner pour moi, je désactive les animations lorsque la liste n'est pas inactive. Il s'agit de l'extension qui résout ce problème:

 fun RecyclerView.executeSafely(func : () -> Unit) {
        if (scrollState != RecyclerView.SCROLL_STATE_IDLE) {
            val animator = itemAnimator
            itemAnimator = null
            func()
            itemAnimator = animator
        } else {
            func()
        }
    }

Ensuite, vous pouvez mettre à jour votre adaptateur comme ça

list.executeSafely {
  adapter.updateICursor(newCursor)
}

0

Si le problème se produit après le multi-touch, vous pouvez désactiver le multi-touch avec

android:splitMotionEvents="false" 

dans le fichier de mise en page.


-1

Si vos données changent beaucoup, vous pouvez utiliser

 mAdapter.notifyItemRangeChanged(0, yourData.size());

ou certains éléments dans vos modifications de jeu de données, vous pouvez utiliser

 mAdapter.notifyItemChanged(pos);

Pour une utilisation détaillée des méthodes, vous pouvez vous référer au document , en quelque sorte, essayez de ne pas l'utiliser directement mAdapter.notifyDataSetChanged().


2
l'utilisation notifyItemRangeChangedproduit également le même plantage.
lionelmessi

Cela convient à certaines situations. Peut-être que vous avez mis à jour votre ensemble de données dans le thread d'arrière-plan et le thread d'interface utilisateur, cela entraînera également une incohérence. Si vous mettez uniquement à jour l'ensemble de données dans le thread d'interface utilisateur, cela fonctionnera.
Arron Cao
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.