RecyclerView e java.lang.IndexOutOfBoundsException: Inconsistência detectada. Posição inválida do adaptador do suporte de exibiçãoViewHolder em dispositivos Samsung

253

Eu tenho uma visão do reciclador que funciona perfeitamente em todos os dispositivos, exceto na Samsung. Na Samsung, eu recebo

java.lang.IndexOutOfBoundsException: Inconsistência detectada. Posição do adaptador do suporte da vista inválida

quando eu voltar ao fragmento com a visão do reciclador de outra atividade.

Código do adaptador:

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

Exceção:

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)

Como posso consertar isso?

Владимир Фишер
fonte
quando você volta, seus dados são os mesmos de quando sai da página?
khusrav 5/09/2015
Estou gatting mesmo problema como u resolver ....
Ashvin solanki
@ Владимир Você encontrou a resposta definitiva?
Alireza Noorali
No meu caso, foi porque eu comecei a tarefa assíncrona. E quando um deles é concluído antes do outro e o usuário rola para baixo e, enquanto isso, outro usuário conclui e atualiza o usuário do adaptador pode obter essa exceção porque a segunda tarefa retornou menos quantidade de dados
Vasif

Respostas:

195

Esse problema é causado por RecyclerViewdados modificados em segmentos diferentes. A melhor maneira é verificar todo o acesso a dados. E uma solução alternativa está sendo resolvida LinearLayoutManager.

Resposta anterior

Na verdade, havia um bug no RecyclerView e o suporte 23.1.1 ainda não foi corrigido.

Para uma solução alternativa, observe que o backtrace empilha, se conseguirmos capturar isso Exceptionem uma classe, ele poderá pular essa falha. Para mim, eu crio LinearLayoutManagerWrappere substituo o 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");
        }
    }
}

Em seguida, defina-o como RecyclerView:

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

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

Na verdade, pegue essa exceção e parece não ter nenhum efeito colateral ainda.

Além disso, se você usar GridLayoutManagerou StaggeredGridLayoutManagerprecisar criar um wrapper para ele.

Aviso: O RecyclerViewpode estar em um estado interno errado.

sakiM
fonte
1
onde exatamente você coloca isso? no adaptador ou atividade?
27615 Steve Jobs (Kamau)
estender um LinearLayoutManagere substituir isso. Eu vou uma adição na minha resposta.
SakiM 30/11/2015
14
code.google.com/p/android/issues/detail?id=158046 resposta nº 12 disse que não faça isso.
Robert
ummm, você está certo. Parece difícil desativar todas as possíveis modificações de thread que não sejam da interface do usuário no meu aplicativo. Manterei isso apenas como uma solução alternativa.
Sakim
1
Para o meu caso, estou fazendo o mesmo tópico. mDataHolder.get (). removeAll (mHiddenGenre); mAdapter.notifyItemRangeRemoved (mExpandButtonPosition, mHiddenGenre.size ());
JehandadK 14/09
73

Este é um exemplo para atualizar dados com conteúdo completamente novo. Você pode modificá-lo facilmente para atender às suas necessidades. Eu resolvi isso no meu caso chamando:

notifyItemRangeRemoved(0, previousContentSize);

antes:

notifyItemRangeInserted(0, newContentSize);

Esta é a solução correta e também é mencionada neste post por um membro do projeto AOSP.

caixa
fonte
2
Esta solução funciona para mim Eu tentei muitas respostas aqui, mas eles não funcionam (eu não testar a primeira solução thaugh)
AndroLife
O problema é que esses métodos criam essa inconsistência, mesmo quando executados no mesmo encadeamento.
JehandadK 14/09
Eu não uso notifyItemRangeInsertede tem esse problema com alguns dispositivos Samsung
USER25
E bastante fora de tópico aqui. O autor não usounotifyItemRangeInserted
user25
1
Obrigado! Isso me ajudou.
DmitryKanunnikoff
35

Eu enfrentei esse problema uma vez e resolvi isso envolvendo as LayoutManageranimações preditivas desativadas e desativadas.

Aqui está um exemplo:

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

E configure-o para RecyclerView:

RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManagerWrapper(context, LinearLayoutManager.VERTICAL, false);
hcknl
fonte
isso parece funcionar para mim, mas você pode dizer por que isso funciona?
Dennis Anderson
Corrigido para mim também. Como você previu que essa pode ser a causa desse acidente.
Rahul Rastogi 10/08/19
1
A classe base do método LinearLayoutManager suportaPredictiveAnimations () retorna false por padrão. O que estamos obtendo substituindo o método aqui? public boolean supportsPredictiveItemAnimations() { return false; }
M. Hig
1
@ M.Hig A documentação para LinearLayoutManagerdiz que o padrão é falso, mas essa declaração é falsa :-( O código descompilado para LinearLayoutManagertem o seguinte: public boolean supportPredictiveItemAnimations () {return this.mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd ;}
Clyde
Uso diff utils para atualizar meu adaptador de exibição de reciclador e esta resposta corrigiu uma falha. Muito obrigado, querido autor!
Eugene P.
29

Nova resposta: use o DiffUtil para todas as atualizações do RecyclerView. Isso ajudará no desempenho e no bug acima. Veja aqui

Resposta anterior: Isso funcionou para mim. A chave é não usar notifyDataSetChanged()e fazer as coisas certas na ordem correta:

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());
}
Bolling
fonte
1
Esta é a solução mais completa com uma boa explicação. Obrigado!
Sakiboy
qual é o objetivo de usar o notifyitemrangeinserted em vez de notifydatasetchanged (), @Bolling.
Ankur_009 17/0418
@FilipLuch Você pode explicar o porquê?
Sreekanth Karumanaghat
3
@SreekanthKaruman tem certeza, não sei por que não expliquei o motivo. Basicamente, ele limpa e recria todos os itens da lista. Como nos resultados de pesquisa, muitas vezes você obtém os mesmos itens ou, quando a atualização é concluída, você obtém os mesmos itens e acaba recriando tudo, o que é um desperdício de desempenho. Use DiffUtils em vez disso e atualize apenas as alterações em vez de todos os itens. É como ir de A a Z toda vez, mas você só mudou o F lá.
Filip Luchianenco
2
DiffUtil é um tesouro escondido. Obrigado por compartilhar!
Sileria 15/03/19
22

Razões que causaram esse problema:

  1. Um problema interno no Recycler quando as animações de itens estão ativadas
  2. Modificação nos dados do Recycler em outro encadeamento
  3. Chamar métodos de notificação de maneira incorreta

SOLUÇÃO:

----------------- SOLUÇÃO 1 ---------------

  • Captura da exceção (não recomendado especialmente pelo motivo 3)

Crie um LinearLayoutManager personalizado como a seguir e defina-o como 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");
                }

            }
        }

Em seguida, defina o RecyclerVIew Layout Manager da seguinte maneira:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUÇÃO 2 ---------------

  • Desativar animações de itens (corrige o problema se isso causasse o motivo 1):

Novamente, crie um Linear Layout Manager personalizado da seguinte maneira:

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

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

Em seguida, defina o RecyclerVIew Layout Manager da seguinte maneira:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUÇÃO 3 ---------------

  • Esta solução corrige o problema se causado pelo motivo # 3. Você precisa se certificar de que está usando os métodos de notificação da maneira correta. Como alternativa, use o DiffUtil para lidar com a mudança de maneira inteligente, fácil e suave. Usando o DiffUtil no Android RecyclerView

----------------- SOLUÇÃO 4 ---------------

  • Pelo motivo 2, é necessário verificar todos os acessos de dados à lista de recicladores e garantir que não haja modificações em outro encadeamento.
Islam Assi
fonte
isso funcionou no meu cenário, não posso usar o DiffUtil porque tenho componentes personalizados para recicladores e adaptadores, e o erro ocorre exatamente em cenários específicos conhecidos, eu só precisava corrigi-lo SEM recorrer à remoção de animadores de itens, então apenas envolveu-o em um try & captura
RJFares
17

Eu tive um problema parecido.

Problema no código de erro abaixo:

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

Solução:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize, messageListHistory.size() -prevSize);
Vandai Doan
fonte
Isso funcionou muito bem para mim! Não sei por que simplesmente não podemos usar newList.size() - 1.
Wasdfakhtar 20/12/19
15

De acordo com esse problema , o problema foi resolvido e provavelmente foi lançado em algum momento próximo ao início de 2015. Uma citação desse mesmo segmento :

Está especificamente relacionado à chamada notifyDataSetChanged. [...]

Aliás, eu recomendo fortemente não usar o notifyDataSetChanged porque mata animações e desempenho. Também neste caso, o uso de eventos de notificação específicos contornará o problema.

Se você ainda estiver tendo problemas com uma versão recente da biblioteca de suporte, sugiro revisar suas chamadas para notifyXXX(especificamente, seu uso de notifyDataSetChanged) dentro do adaptador, para garantir que você esteja cumprindo o RecyclerView.Adaptercontrato (um tanto delicado / obscuro) . Certifique-se também de emitir essas notificações no thread principal.

stkent
fonte
16
não, concordo com sua parte sobre desempenho, mas notifyDataSetChanged () não mata animações, para animar usando notifyDataSetChanged (), a) chame setHasStableIds (true) no seu objeto RecyclerView.Adapter eb) substitua getItemId dentro do seu adaptador para retornar um valor longo exclusivo para cada linha e check-out, as animações fazer o trabalho
PirateApp
@PirateApp Você deve fazer seu comentário como resposta. Eu tentei e está funcionando bem.
Mr5
Não é verdade! Ainda receba relatórios do Google Console sobre esse problema. E o dispositivo, é claro, é a Samsung -Samsung Galaxy J3(2017) (j3y17lte), Android 8.0
user25
10

Eu tive o mesmo problema. Foi causado porque atrasei a notificação do adaptador sobre a inserção do item.

Mas ViewHoldertentou redesenhar alguns dados em sua exibição e iniciou a RecyclerViewcontagem e contagem de crianças contadas - naquele momento falhou (a lista de itens e seu tamanho já estavam atualizados, mas o adaptador ainda não foi notificado).

porfirion
fonte
8

Isso acontece quando você especifica a posição incorreta para o notifyItemChanged, notifyItemRangeInserted etc.

Antes: (Errôneo)

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

Depois: (Correto)

 public void addData(List<ChannelItem> list) {
  int initialSize = mChannelItemList.size();
  mChannelItemList.addAll(list);
  notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); //Correct position 
 }
Saurabh Padwekar
fonte
1
Por que notifyItemRangeInserted(initialSize, mChannelItemList.size()-1);e não notifyItemRangeInserted(initialSize, list.size());?
CoolMind 02/07
Undestood. Você misturou initialSizee listtamanho. Portanto, as duas variantes estão erradas.
CoolMind 23/07/19
Para mim, funciona, notifyItemRangeInserted(initialSize, list.size()-1);mas eu não entendo. Por que tenho que reduzir o tamanho inserido em um para o itemCount?
Plexo #
7

outro motivo pelo qual esse problema ocorre é quando você chama esses métodos com índices incorretos (índices que NÃO ocorreram inseridos ou removidos)

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

verifique os parâmetros indexados nesses métodos e verifique se eles são precisos e corretos.

Amir Ziarati
fonte
2
Este foi o meu problema. A exceção ocorre ao adicionar nada na lista.
Rasel
6

Esse bug ainda não foi corrigido na 23.1.1, mas uma solução comum seria capturar a exceção.

Farooq AR
fonte
18
Pegue onde, exatamente? O único código no rastreamento de pilha é o código Android nativo.
howettl
1
Veja como a resposta @saki_M.
Renan Bandeira
Isso realmente funciona para você, embora Renan? Você já testou a correção por um tempo? O bug ocorre apenas ocasionalmente, então só vou ver se isso funciona com o tempo.
Simon
Isso realmente funciona, mas algumas visões de crianças nas minhas são inconsistentes.
david
@ David Inconsistentemente permanece Qual é a consequência disso?
Sreekanth Karumanaghat
4

Esse problema é causado pelos dados do RecyclerView modificados em threads diferentes

Pode confirmar o encadeamento como um problema e, desde que deparei com o problema, o RxJava está se tornando cada vez mais popular: verifique se você está usando .observeOn(AndroidSchedulers.mainThread())sempre que está ligandonotify[whatever changed]

exemplo de código do adaptador:

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

    [...]

    @Override
    public void onNext(AuxDataStructure o) {
        [notify here]
    }
});
Philipp
fonte
Estou no Thread principal ao chamar DiffUtil.calculateDiff (diffUtilForecastItemChangesAnlayser (this.mWeatherForecatsItemWithMainAndWeathers, weatherForecastItems)). DispatchUpdatesTo (this); o log está claro noThread: Thread [main, 5, main]
Mathias Seguy Android2ee
4

No meu caso, toda vez que eu chamo notifyItemRemoved (0), ele travava. Aconteceu que eu ajustei setHasStableIds(true)e getItemIdretornei a posição do item. Acabei atualizando-o para retornar o hashCode()ID exclusivo do item ou auto-definido, que resolveu o problema.

Arst
fonte
4

No meu caso, eu estava tendo esse problema devido a atualizações de dados do servidor (estou usando o Firebase Firestore) e, enquanto o primeiro conjunto de dados está sendo processado pelo DiffUtil em segundo plano, outro conjunto de atualizações de dados ocorre e causa um problema de simultaneidade. iniciando outro DiffUtil.

Em resumo, se você estiver usando o DiffUtil em um encadeamento em segundo plano, que retornará ao encadeamento principal para despachar os resultados para o RecylerView, será possível obter esse erro quando várias atualizações de dados ocorrerem em pouco tempo.

Resolvi isso seguindo os conselhos desta maravilhosa explicação: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2

Só para explicar a solução é enviar as atualizações enquanto a atual está sendo executada no Deque. O deque pode executar as atualizações pendentes assim que a atual terminar, manipulando todas as atualizações subsequentes, mas evitando erros de inconsistência também!

Espero que isso ajude, porque este me fez coçar a cabeça!

dejavu89
fonte
Obrigado pelo link!
CoolMind 02/07
3

O problema ocorreu para mim somente quando:

Eu criei o adaptador com uma lista vazia . Então eu inseri itens e liguei notifyItemRangeInserted.

Solução:

Resolvi isso criando o adaptador somente depois de ter o primeiro pedaço de dados e inicializando-o imediatamente. O próximo pedaço poderia ser inserido e notifyItemRangeInsertedchamado sem nenhum problema.

Willi Mentzel
fonte
Eu não acho que é a razão. Eu tenho muitos adaptadores com listas vazias e adicionei itens com notifyItemRangeInserted, mas nunca tive essa exceção por lá.
CoolMind 24/07/19
3

Meu problema era que, apesar de limpar a lista de matrizes que contém o modelo de dados para a exibição do reciclador, não notifiquei o adaptador sobre essa alteração; portanto, ele tinha dados obsoletos do modelo anterior. O que causou confusão sobre a posição do suporte da vista. Para corrigir isso, sempre notifique o adaptador que o conjunto de dados foi alterado antes de atualizar novamente.

Remario
fonte
ou simplesmente notifique se o item foi removido
Remario 23/04
meu modelo usa a referência dos isso é contêiner porque
Remario
3

No meu caso, eu estava alterando os dados anteriormente dentro de um encadeamento com mRecyclerView.post (novo Runnable ...) e depois novamente alterei os dados no encadeamento da interface do usuário, o que causou inconsistência.

Niroj Shr
fonte
1
eu tenho a mesma situação que você, como você resolveu isso? graças
baderkhane
2

O erro pode ser causado por suas alterações serem inconsistentes com o que você está notificando. No meu caso:

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

O que eu obviamente tinha que fazer:

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

No meu caso, o problema era que eu usei notifyDataSetChanged quando a quantidade de dados recém-carregados era menor que os dados iniciais. Essa abordagem me ajudou a:

adapter.notifyItemRangeChanged(0, newAmountOfData + 1);
adapter.notifyItemRangeRemoved(newAmountOfData + 1, previousAmountOfData);
pretty_fennec
fonte
Por que notifyDataSetChangeddepende de novos dados? Eu pensei que iria atualizar lista inteira.
CoolMind 24/07/19
2

Eu tive o mesmo problema.

Meu aplicativo usa componentes de navegação com um fragmento contendo meu recyclerView. Minha lista foi exibida bem na primeira vez em que o fragmento foi carregado ... mas, ao navegar e voltar, esse erro ocorreu.

Ao navegar, o ciclo de vida do fragmento passou apenas pelo onDestroyView e, ao retorná-lo, começou pelo onCreateView. No entanto, meu adaptador foi inicializado no onCreate do fragmento e não foi reinicializado ao retornar.

A correção foi inicializar o adaptador no onCreateView.

Espero que isso ajude alguém.

Loren
fonte
0

Eu recebi esse erro porque estava chamando um método por engano para remover uma linha específica da minha reciclerview várias vezes. Eu tinha um método como:

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

Eu estava acidentalmente chamando esse método três vezes em vez de uma vez, então a segunda vez locfoi -1 e o erro foi dado ao tentar removê-lo. As duas correções eram para garantir que o método fosse chamado apenas uma vez e também para adicionar uma verificação de integridade como esta:

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

Eu tenho o mesmo problema e li que isso aconteceu apenas nos telefones Samsung ... Mas a realidade mostrou que isso acontece em muitas marcas.

Após o teste, percebi que isso acontece apenas quando você rolar rapidamente o RecyclerView e voltar com o botão Voltar ou o botão Acima. Então eu coloquei dentro do botão Para cima e onBackpressed o trecho abaixo:

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

Com esta solução, você apenas carrega uma nova lista de matriz no adaptador e um novo adaptador no recyclerView e termina a atividade.

Espero que ajude alguém

Farmaker
fonte
0

Eu recebi esse erro porque estava chamando "notifyItemInserted" duas vezes por engano.

Feuby
fonte
0

No meu caso, tive mais de 5000 itens na lista. Meu problema era que, ao rolar a exibição do reciclador, às vezes o "onBindViewHolder" é chamado enquanto o método "myCustomAddItems" está alterando a lista.

Minha solução foi adicionar "sincronizado (syncObject) {}" a todos os métodos que alteram a lista de dados. Dessa forma, a qualquer momento, apenas um método pode ler esta lista.

user3193413
fonte
0

No meu caso, os dados do adaptador foram alterados. E eu estava errado usar notifyItemInserted () para essas alterações. Quando uso o notifyItemChanged, o erro desapareceu.

oiyio
fonte
0

Encontrei o mesmo problema quando removi e atualizei os itens da lista ... Após dias de investigação, acho que finalmente encontrei uma solução.

O que você precisa fazer é primeiro fazer todo o conteúdo notifyItemChangedda sua lista e só depois fazer tudo notifyItemRemoved em ordem decrescente

Espero que isso ajude as pessoas que estão enfrentando o mesmo problema ...

Talihawk
fonte
0

Estou usando um Cursor, portanto não posso usar o DiffUtils como proposto nas respostas populares. Para fazê-lo funcionar, desabilito as animações quando a lista não está ociosa. Esta é a extensão que corrige esse problema:

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

Então você pode atualizar seu adaptador assim

list.executeSafely {
  adapter.updateICursor(newCursor)
}
joecks
fonte
0

Se o problema ocorrer após o multitoque, você poderá desativar o multitoque com

android:splitMotionEvents="false" 

no arquivo de layout.

intra
fonte
-1

Se seus dados mudarem muito, você pode usar

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

ou alguns itens únicos no seu conjunto de dados são alterados, você pode usar

 mAdapter.notifyItemChanged(pos);

Para o uso de métodos detalhados, você pode consultar o documento , de certa forma, tente não usar diretamente mAdapter.notifyDataSetChanged().

Arron Cao
fonte
2
usar notifyItemRangeChangedtambém produz a mesma falha.
Lionel Messi
Isso combina com alguma situação. Talvez você tenha atualizado seu conjunto de dados no thread de segundo plano e no UI, isso também causará inconsistência. Se você atualizar apenas o conjunto de dados no thread da interface do usuário, ele funcionará.
Arron Cao