RecyclerView y java.lang.IndexOutOfBoundsException: Inconsistencia detectada. Posición del adaptador del soporte de visualización no válida ViewHolder en dispositivos Samsung

253

Tengo una vista de reciclador que funciona perfectamente en todos los dispositivos, excepto Samsung. En Samsung, tengo

java.lang.IndexOutOfBoundsException: Inconsistencia detectada. Posición del adaptador del soporte de la vista no válida

cuando vuelvo al fragmento con la vista de reciclador de otra actividad.

Código del 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);
        }
    }
}

Excepción:

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)

¿Cómo puedo arreglar esto?

Владимир Фишер
fuente
cuando regresas, ¿tus datos son los mismos que cuando sales de la página?
khusrav
Estoy reuniendo el mismo problema, ¿cómo lo resuelves ...
Ashvin solanki
@ Владимир ¿Encontró la respuesta definitiva?
Alireza Noorali
En mi caso, fue porque comencé la tarea asíncrona, y cuando uno de ellos se completa antes que otro y el usuario se desplaza hacia abajo y mientras tanto otro completa y actualiza el usuario del adaptador puede obtener esa excepción porque la segunda tarea devolvió menos cantidad de datos
Vasif

Respuestas:

195

Este problema es causado por RecyclerViewdatos modificados en diferentes hilos. La mejor manera es verificar todo el acceso a datos. Y se está resolviendo una solución alternativa LinearLayoutManager.

Respuesta anterior

En realidad, hubo un error en RecyclerView y el soporte 23.1.1 todavía no se solucionó.

Para una solución alternativa, tenga en cuenta que las pilas de retroceso, si podemos atrapar esto Exceptionen alguna clase, puede omitir este bloqueo. Para mí, creo LinearLayoutManagerWrappery anulo el 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");
        }
    }
}

Luego configúrelo en RecyclerView:

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

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

En realidad, capta esta excepción y parece que todavía no hay ningún efecto secundario.

Además, si usa GridLayoutManagero StaggeredGridLayoutManagerdebe crear un contenedor para ello.

Aviso: RecyclerViewpuede estar en un estado interno incorrecto.

sakiM
fuente
1
¿Dónde exactamente pones esto? en adaptador o actividad?
Steve Kamau
extienda LinearLayoutManageray anule esto. Haré una adición en mi respuesta.
sakiM
14
code.google.com/p/android/issues/detail?id=158046 la respuesta # 12 dijo que no hagas eso.
Robert
ummm, tienes razón. Parece difícil desactivar todas las posibles modificaciones sin hilos de interfaz de usuario en mi aplicación, mantendré esto solo como una solución alternativa.
sakiM
1
Para mi caso lo estoy haciendo en el mismo hilo. mDataHolder.get (). removeAll (mHiddenGenre); mAdapter.notifyItemRangeRemoved (mExpandButtonPosition, mHiddenGenre.size ());
JehandadK
73

Este es un ejemplo para actualizar datos con contenido completamente nuevo. Puede modificarlo fácilmente para adaptarlo a sus necesidades. Resolví esto en mi caso llamando:

notifyItemRangeRemoved(0, previousContentSize);

antes de:

notifyItemRangeInserted(0, newContentSize);

Esta es la solución correcta y también es mencionada en esta publicación por un miembro del proyecto AOSP.

caja
fuente
2
Esta solución funciona para mí. Intenté muchas respuestas aquí pero no funcionan (no probé la primera solución thaugh)
AndroLife
El problema es que el uso de estos métodos crea esa inconsistencia, incluso cuando se realiza en el mismo hilo.
JehandadK
No uso notifyItemRangeInsertedy tengo este problema con algunos dispositivos Samsung
usuario25
Y bastante fuera de tema aquí. El autor no usónotifyItemRangeInserted
usuario25
1
¡Gracias! Eso me ayudo.
DmitryKanunnikoff
35

Enfrenté este problema una vez, y lo resolví envolviendo el LayoutManager y desactivando las animaciones predictivas.

Aquí un ejemplo:

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

Y configúrelo en RecyclerView:

RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManagerWrapper(context, LinearLayoutManager.VERTICAL, false);
hcknl
fuente
Esto parece funcionar para mí, pero ¿puedes decir por qué funciona?
Dennis Anderson
Arreglado para mí también. Cómo predijo que esta podría ser la causa de este bloqueo.
Rahul Rastogi
1
La clase base del método LinearLayoutManager admitePredictiveAnimations () devuelve falso de forma predeterminada. ¿Qué estamos obteniendo al anular el método aquí? public boolean supportsPredictiveItemAnimations() { return false; }
M. Hig
1
@ M.Hig La documentación de LinearLayoutManagerdice que el valor predeterminado es falso, pero esa declaración es falsa :-( El código descompilado para LinearLayoutManagertiene esto: public boolean supportsPredictiveItemAnimations () {return this.mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd ;}
Clyde
Uso diff utils para actualizar mi adaptador de vista de reciclador y esta respuesta solucionó un bloqueo. Muchas gracias, querido autor!
Eugene P.
29

Nueva respuesta: use DiffUtil para todas las actualizaciones de RecyclerView. Esto ayudará con el rendimiento y el error anterior. Mira aquí

Respuesta anterior: Esto funcionó para mí. La clave es no usar notifyDataSetChanged()y hacer las cosas correctas en el orden correcto:

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
fuente
1
Esta es la solución más completa con una buena explicación. ¡Gracias!
Sakiboy
entonces, ¿cuál es el propósito de utilizar notifyitemrangeinserted en lugar de notifydatasetchanged (), @Bolling.
Ankur_009
@FilipLuch ¿Puedes explicar por qué?
Sreekanth Karumanaghat
3
@SreekanthKarumanaghat seguro, no sé por qué no expliqué la razón. Básicamente, borra y luego recrea todos los elementos de la lista. Al igual que en los resultados de búsqueda, muy a menudo obtienes los mismos elementos, o cuando se realiza la actualización, obtienes los mismos elementos y luego terminas recreando todo, lo cual es una pérdida de rendimiento. Utilice DiffUtils en su lugar y solo actualice los cambios en lugar de todos los elementos. Es como ir de la A a la Z cada vez, pero solo cambiaste la F allí.
Filip Luchianenco
2
DiffUtil es un tesoro escondido. ¡Gracias por compartir!
Sileria
22

Las razones causaron este problema:

  1. Un problema interno en Recycler cuando las animaciones de elementos están habilitadas
  2. Modificación en los datos del Reciclador en otro hilo
  3. Llamar a métodos de notificación de manera incorrecta

SOLUCIÓN:

----------------- SOLUCIÓN 1 ---------------

  • Capturar la excepción (no recomendado especialmente por la razón # 3)

Cree un LinearLayoutManager personalizado como el siguiente y configúrelo en 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");
                }

            }
        }

Luego configure RecyclerVIew Layout Manager de la siguiente manera:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUCIÓN 2 ---------------

  • Deshabilite las animaciones de elementos (soluciona el problema si causó el motivo # 1):

Nuevamente, cree un Administrador de diseño lineal personalizado de la siguiente manera:

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

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

Luego configure RecyclerVIew Layout Manager de la siguiente manera:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUCIÓN 3 ---------------

  • Esta solución soluciona el problema si es causado por la razón # 3. Debe asegurarse de estar utilizando los métodos de notificación de la manera correcta. Alternativamente, use DiffUtil para manejar el cambio de una manera inteligente, fácil y sin problemas. Usando DiffUtil en Android RecyclerView

----------------- SOLUCIÓN 4 ---------------

  • Por la razón # 2, debe verificar todo el acceso a datos a la lista de recicladores y asegurarse de que no haya modificaciones en otro hilo.
Islam Assi
fuente
esto funcionó en mi escenario, no puedo usar DiffUtil porque tengo componentes personalizados para recicladores y adaptadores, y el error ocurre exactamente en escenarios específicos que se conocen, solo necesitaba parchearlo sin recurrir a la eliminación de animadores de elementos, así que simplemente envuelto en un try & catch
RJFares
17

Tuve un problema similar.

Problema en el código de error a continuación:

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

Solución:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize, messageListHistory.size() -prevSize);
Vandai Doan
fuente
¡Esto funciono muy bien para mi! Sin newList.size() - 1embargo, no estoy seguro de por qué no podemos usarlo .
waseefakhtar
15

De acuerdo con este problema , el problema se ha resuelto y probablemente se publicó en algún momento cerca del comienzo de 2015. Una cita de ese mismo hilo :

Está específicamente relacionado con llamar a notifyDataSetChanged. [...]

Por cierto, le recomiendo no utilizar notifyDataSetChanged porque mata las animaciones y el rendimiento. También para este caso, el uso de eventos de notificación específicos evitará el problema.

Si todavía tiene problemas con una versión reciente de la biblioteca de soporte, le sugiero que revise sus llamadas notifyXXX(específicamente, su uso de notifyDataSetChanged) dentro de su adaptador, para asegurarse de que cumple con el RecyclerView.Adaptercontrato (algo delicado / oscuro) . También asegúrese de emitir esas notificaciones en el hilo principal.

stkent
fuente
16
en realidad no, estoy de acuerdo con su parte sobre el rendimiento, pero notifyDataSetChanged () no elimina las animaciones, para animar usando notifyDataSetChanged (), a) llame a setHasStableIds (true) en su objeto RecyclerView.Adapter yb) anule getItemId dentro de su adaptador para devolver un valor largo único para cada fila y échale un vistazo, las animaciones funcionan
PirateApp
@PirateApp Debería considerar hacer su comentario como respuesta. Lo he intentado y está funcionando bien.
mr5
¡No es verdad! Todavía recibo informes de Google Console sobre este problema. Y el dispositivo, por supuesto, es Samsung -Samsung Galaxy J3(2017) (j3y17lte), Android 8.0
user25
10

Yo tuve el mismo problema. Fue causado porque retrasé la notificación del adaptador sobre la inserción del elemento.

Pero ViewHoldertrató de volver a dibujar algunos datos a su vista y comenzó a RecyclerViewmedir y volver a contar el recuento de niños; en ese momento se bloqueó (la lista de elementos y su tamaño ya se actualizaron, pero el adaptador aún no se notificó).

porfirion
fuente
8

Esto sucede cuando especifica la posición incorrecta para notifyItemChanged, notifyItemRangeInserted, etc. Para mí:

Antes: (erróneo)

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

Después: (correcto)

 public void addData(List<ChannelItem> list) {
  int initialSize = mChannelItemList.size();
  mChannelItemList.addAll(list);
  notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); //Correct position 
 }
Saurabh Padwekar
fuente
1
¿Por qué notifyItemRangeInserted(initialSize, mChannelItemList.size()-1);y no notifyItemRangeInserted(initialSize, list.size());?
CoolMind
No entendido Te confundiste initialSizey listdimensionaste. Entonces, ambas variantes están mal.
CoolMind
Para mí funciona, notifyItemRangeInserted(initialSize, list.size()-1);pero no lo entiendo. ¿Por qué tengo que reducir el tamaño insertado en uno para el itemCount?
plexo
7

Otra razón por la que ocurre este problema es cuando llama a estos métodos con índices incorrectos (los índices que NO han sucedido se insertan o eliminan en ellos)

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

verifique los parámetros de indexación de estos métodos y asegúrese de que sean precisos y correctos.

Amir Ziarati
fuente
2
Este fue mi problema. Se produce una excepción al no agregar nada en la lista.
Rasel
6

Este error aún no se solucionó en 23.1.1, pero una solución común sería detectar la excepción.

Farooq AR
fuente
18
Atrápalo donde, exactamente? El único código en el seguimiento de la pila es el código nativo de Android.
howettl
1
Atrápalo como la respuesta @saki_M.
Renan Bandeira
¿Esto realmente funciona para ti aunque Renan? ¿Has probado la solución por un tiempo? El error solo ocurre ocasionalmente, así que solo veré si esto funciona con el tiempo.
Simon
Esto realmente funciona, pero algunas vistas secundarias en la mía son restos inconsistentes.
David
@david sigue siendo inconsistente ¿cuál es la consecuencia de esto?
Sreekanth Karumanaghat
4

Este problema es causado por RecyclerView Data modificado en diferentes hilos

Puede confirmar el enhebrado como un problema y desde que me encontré con el problema y RxJava se está volviendo cada vez más popular: asegúrese de usarlo .observeOn(AndroidSchedulers.mainThread())cada vez que llamenotify[whatever changed]

ejemplo de código del adaptador:

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

    [...]

    @Override
    public void onNext(AuxDataStructure o) {
        [notify here]
    }
});
Philipp
fuente
Estoy en el subproceso principal al llamar a DiffUtil.calculateDiff (diffUtilForecastItemChangesAnlayser (this.mWeatherForecatsItemWithMainAndWeathers, weatherForecastItems)). DispatchUpdatesTo (this); el registro está claro en Thread: Thread [main, 5, main]
Mathias Seguy Android2ee
4

En mi caso, cada vez que llamo a notifyItemRemoved (0), se bloquea. Resultó que configuré setHasStableIds(true)y getItemIdacabo de devolver la posición del artículo. Terminé actualizándolo para devolver el ítem hashCode()o la identificación única autodefinida, lo que resolvió el problema.

Primero
fuente
4

En mi caso, recibí este problema debido a que recibí actualizaciones de datos del servidor (estoy usando Firebase Firestore) y mientras DiffUtil procesa el primer conjunto de datos en segundo plano, aparece otro conjunto de actualización de datos que causa un problema de concurrencia iniciando otro DiffUtil.

En resumen, si está utilizando DiffUtil en un subproceso en segundo plano que luego regresa al subproceso principal para enviar los resultados al RecylerView, entonces tiene la posibilidad de obtener este error cuando llegan varias actualizaciones de datos en poco tiempo.

Resolví esto siguiendo los consejos de esta maravillosa explicación: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2

Solo para explicar que la solución es enviar las actualizaciones mientras la actual se ejecuta en Deque. ¡El deque puede ejecutar las actualizaciones pendientes una vez que finalice la actual, por lo tanto, manejar todas las actualizaciones posteriores pero también evitar errores de inconsistencia!

¡Espero que esto ayude porque este me hizo rascarme la cabeza!

dejavu89
fuente
Gracias por el enlace!
CoolMind
3

Se produjo un problema para mí solo cuando:

Creé el adaptador con una lista vacía . Luego inserté elementos y llamé notifyItemRangeInserted.

Solución:

Resolví esto creando el Adaptador solo después de tener la primera porción de datos e inicializándolo con él de inmediato. El siguiente fragmento se podría insertar y notifyItemRangeInsertedllamar sin ningún problema.

Willi Mentzel
fuente
No creo que sea la razón. Tengo muchos adaptadores con listas vacías, luego agregué elementos con notifyItemRangeInserted, pero nunca tuve esta excepción allí.
CoolMind
3

Mi problema fue que, aunque borré la lista de la matriz que contiene el modelo de datos para la vista del reciclador, no notifiqué al adaptador ese cambio, por lo que tenía datos obsoletos del modelo anterior. Lo que causó la confusión sobre la posición del titular de la vista. Para solucionar esto, siempre notifique al adaptador que el conjunto de datos ha cambiado antes de actualizar nuevamente.

Remario
fuente
o simplemente notificar si se eliminó el artículo en su lugar
Remario
mi modelo usa la referencia del contenedor por eso
Remario
3

En mi caso, estaba cambiando los datos previamente dentro de un hilo con mRecyclerView.post (nuevo Runnable ...) y luego volví a cambiar los datos en el hilo de la interfaz de usuario, lo que causó inconsistencia.

Niroj Shr
fuente
1
Tengo la misma situación que tú, ¿cómo lo resolviste? gracias
baderkhane
2

El error puede deberse a que sus cambios son inconsistentes con lo que está notificando. En mi caso:

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

Lo que, por supuesto, tuve que hacer:

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

En mi caso, el problema era que usé notifyDataSetChanged cuando la cantidad de datos recién cargados era menor que los datos iniciales. Este enfoque me ayudó:

adapter.notifyItemRangeChanged(0, newAmountOfData + 1);
adapter.notifyItemRangeRemoved(newAmountOfData + 1, previousAmountOfData);
pretty_fennec
fuente
¿Por qué notifyDataSetChangeddepende de nuevos datos? Pensé que actualizaría toda la lista.
CoolMind
2

Tuve el mismo problema.

Mi aplicación utiliza componentes de navegación con un fragmento que contiene mi recyclerView. Mi lista se mostró bien la primera vez que se cargó el fragmento ... pero al navegar y regresar, se produjo este error.

Al navegar lejos, el ciclo de vida del fragmento pasó solo por onDestroyView y al regresar comenzó en onCreateView. Sin embargo, mi adaptador se inicializó en el fragmento onCreate y no se reinicializó al regresar.

La solución fue inicializar el adaptador en onCreateView.

Espero que esto pueda ayudar a alguien.

Loren
fuente
0

Recibí este error porque estaba llamando por error a un método para eliminar una fila específica de mi vista de reciclador varias veces. Tenía un método como:

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

Accidentalmente estaba llamando a este método tres veces en lugar de una vez, por lo que la segunda vez locfue -1 y se produjo el error cuando intentó eliminarlo. Las dos soluciones fueron para garantizar que el método solo se llamara una vez, y también para agregar un control de cordura como este:

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

Tengo el mismo problema y he leído que esto sucedió solo en teléfonos Samsung ... Pero la realidad mostró que esto sucede en muchas marcas.

Después de la prueba, me di cuenta de que esto sucede solo cuando se desplaza rápidamente el RecyclerView y luego retrocede con el botón Atrás o el botón Arriba. Así que puse dentro el botón Arriba y presioné el fragmento a continuación:

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

Con esta solución, simplemente carga una nueva Arraylist en el adaptador y un nuevo adaptador en recyclerView y luego finaliza la actividad.

Espero que ayude a alguien

Farmaker
fuente
0

Recibí este error porque estaba llamando a "notifyItemInserted" dos veces por error.

Feuby
fuente
0

En mi caso, he tenido más de 5000 artículos en la lista. Mi problema fue que al desplazar la vista del reciclador, a veces se llama al "onBindViewHolder" mientras el método "myCustomAddItems" está alterando la lista.

Mi solución fue agregar "sincronizado (syncObject) {}" a todos los métodos que alteran la lista de datos. De esta manera, en cualquier momento, solo un método puede leer esta lista.

usuario3193413
fuente
0

En mi caso, los datos del adaptador cambiaron. Y utilicé incorrectamente notifyItemInserted () para estos cambios. Cuando uso notifyItemChanged, el error ha desaparecido.

oiyio
fuente
0

Me encontré con el mismo problema cuando eliminé y actualicé los elementos de la lista ... Después de días de investigación, creo que finalmente encontré una solución.

Lo que debe hacer es primero hacer toda notifyItemChangedsu lista y solo luego hacer todo notifyItemRemoved en orden descendente

Espero que esto ayude a las personas que se encuentran con el mismo problema ...

Talihawk
fuente
0

Estoy usando un cursor, por lo que no puedo usar los DiffUtils como se propone en las respuestas populares. Para que funcione para mí, deshabilito las animaciones cuando la lista no está inactiva. Esta es la extensión que soluciona este problema:

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

Entonces puedes actualizar tu adaptador así

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

Si el problema ocurre después de la función multitáctil, puede desactivarla con

android:splitMotionEvents="false" 

en el archivo de diseño.

intra
fuente
-1

Si sus datos cambian mucho, puede usar

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

o algunos elementos individuales en su conjunto de datos cambian, puede usar

 mAdapter.notifyItemChanged(pos);

Para el uso detallado de los métodos, puede consultar el documento , de alguna manera, intente no usarlo directamente mAdapter.notifyDataSetChanged().

Arron Cao
fuente
2
el uso notifyItemRangeChangedtambién produce el mismo bloqueo.
lionelmessi
Eso se adapta a alguna situación. Tal vez haya actualizado su conjunto de datos tanto en el subproceso de fondo como en el subproceso de la interfaz de usuario, esto también causará inconsistencia. Si solo actualiza el conjunto de datos en el subproceso de la interfaz de usuario, funcionará.
Arron Cao