RecyclerView dan java.lang.IndexOutOfBoundsException: Ketidakkonsistenan terdeteksi. Posisi adaptor pemegang view viewView tidak ada di perangkat Samsung


253

Saya memiliki pandangan pendaur ulang yang bekerja dengan baik pada semua perangkat kecuali Samsung. Di Samsung, saya mengerti

java.lang.IndexOutOfBoundsException: Ketidakkonsistenan terdeteksi. Tampilan adapter pemegang viewViewHolder tidak valid

ketika saya akan kembali ke fragmen dengan tampilan pendaur ulang dari aktivitas lain.

Kode adaptor:

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

Pengecualian:

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)

Bagaimana saya bisa memperbaikinya?


ketika Anda kembali, apakah data Anda sama seperti ketika Anda meninggalkan halaman?
khusrav

saya sedang mengatasi masalah yang sama bagaimana Anda menyelesaikan ....
Ashvin solanki

@ Владимир Apakah Anda menemukan jawaban yang pasti?
Alireza Noorali

Dalam kasus saya, itu karena saya mulai mengerjakan tugas async, Dan ketika salah satu dari mereka selesai sebelum yang lain dan pengguna menggulir ke bawah dan sementara itu yang lain menyelesaikan dan memperbarui pengguna adaptor dapat memperoleh pengecualian seperti itu karena tugas kedua mengembalikan lebih sedikit jumlah data
Vasif

Jawaban:


195

Masalah ini disebabkan oleh RecyclerViewdata yang dimodifikasi di utas berbeda. Cara terbaik adalah memeriksa semua akses data. Dan solusinya sedang membungkus LinearLayoutManager.

Jawaban sebelumnya

Sebenarnya ada bug di RecyclerView dan dukungan 23.1.1 masih belum diperbaiki.

Untuk solusinya, perhatikan bahwa backtrace menumpuk, jika kita dapat menangkap ini Exceptiondi salah satu kelas tertentu mungkin melewatkan kecelakaan ini. Bagi saya, saya membuat LinearLayoutManagerWrapperdan menimpa 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");
        }
    }
}

Kemudian atur ke RecyclerView:

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

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

Sebenarnya menangkap pengecualian ini, dan tampaknya belum ada efek samping.

Juga, jika Anda menggunakan GridLayoutManageratau StaggeredGridLayoutManagerAnda harus membuat pembungkus untuk itu.

Perhatikan: Kondisi RecyclerViewinternal mungkin salah.


1
di mana tepatnya Anda meletakkan ini? pada adaptor atau aktivitas?
Steve Kamau

memperpanjang LinearLayoutManagerdan menimpa ini. Saya akan menambahkan jawaban saya.
sakiM

14
code.google.com/p/android/issues/detail?id=158046 jawab # 12 mengatakan jangan lakukan itu.
Robert

ummm kamu benar Tampaknya sulit untuk menjinakkan semua potensi modifikasi non-UI-thread di aplikasi saya, saya akan menyimpan ini hanya sebagai solusi.
sakiM

1
Untuk kasus saya, saya lakukan pada utas yang sama. mDataHolder.get (). removeAll (mHiddenGenre); mAdapter.notifyItemRangeRemoved (mExpandButtonPosition, mHiddenGenre.size ());
JehandadK

73

Ini adalah contoh untuk menyegarkan data dengan konten yang sama sekali baru. Anda dapat dengan mudah memodifikasinya agar sesuai dengan kebutuhan Anda. Saya memecahkan ini dalam kasus saya dengan menelepon:

notifyItemRangeRemoved(0, previousContentSize);

sebelum:

notifyItemRangeInserted(0, newContentSize);

Ini adalah solusi yang benar dan juga disebutkan dalam posting ini oleh anggota proyek AOSP.


2
Solusi ini berfungsi untuk saya, saya mencoba banyak jawaban di sini, tetapi tidak berhasil (saya tidak menguji solusi pertama, pencuri)
AndroLife

Masalahnya adalah menggunakan metode ini membuat inkonsistensi itu, bahkan ketika dilakukan pada utas yang sama.
JehandadK

Saya tidak menggunakan notifyItemRangeInserteddan memiliki masalah ini dengan beberapa perangkat Samsung
user25

Dan cukup di luar topik di sini. Penulis tidak menggunakannotifyItemRangeInserted
user25

1
Terima kasih! Itu membantu saya.
DmitryKanunnikoff

35

Saya menghadapi masalah ini satu kali, dan saya menyelesaikannya dengan membungkus LayoutManagerdan menonaktifkan animasi prediksi.

Berikut sebuah contoh:

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

Dan atur ke RecyclerView:

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

ini sepertinya bekerja untuk saya, tetapi dapatkah Anda memberi tahu mengapa ini berhasil?
Dennis Anderson

Tetap untuk saya juga. Bagaimana Anda meramalkan bahwa ini mungkin menjadi penyebab kecelakaan ini.
Rahul Rastogi

1
Kelas dasar metode LinearLayoutManager mendukungPredictiveAnimations () mengembalikan false secara default. Apa yang kita dapatkan dengan mengganti metode di sini? public boolean supportsPredictiveItemAnimations() { return false; }
M. Hig

1
@ M.Hig Dokumentasi untuk LinearLayoutManagermengatakan default adalah salah, tetapi pernyataan itu salah :-( Kode yang didekompilasi untuk LinearLayoutManagermemiliki ini: public boolean supportPredictiveItemAnimations () {return this.mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd ;}
Clyde

Saya menggunakan utils berbeda untuk memperbarui adaptor tampilan pendaur ulang saya dan jawaban ini memperbaiki kerusakan. Terima kasih banyak, penulis terkasih!
Eugene P.

29

Jawaban baru: Gunakan DiffUtil untuk semua pembaruan RecyclerView. Ini akan membantu dengan kinerja dan bug di atas. Lihat disini

Jawaban sebelumnya: Ini berhasil untuk saya. Kuncinya adalah untuk tidak menggunakan notifyDataSetChanged()dan melakukan hal-hal yang benar dalam urutan yang benar:

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

1
Ini adalah solusi paling menyeluruh dengan penjelasan yang bagus. Terima kasih!
Sakiboy

lalu apa tujuan menggunakan notifyitemrangeinserted bukan notifydatasetchanged (), @Bolling.
Ankur_009

@FilipLuch Bisakah Anda menjelaskan alasannya?
Sreekanth Karumanaghat

3
@ SreekanthKarumanaghat yakin, tidak tahu mengapa saya tidak menjelaskan alasannya. Pada dasarnya dia membersihkan lalu menciptakan kembali semua item dalam daftar. Seperti di hasil pencarian, sangat sering Anda mendapatkan item yang sama, atau ketika refresh selesai, Anda mendapatkan item yang sama dan kemudian Anda akan berakhir menciptakan kembali semuanya, yang merupakan pemborosan kinerja. Gunakan DiffUtils sebagai gantinya dan hanya perbarui perubahan daripada semua item. Ini seperti pergi dari A ke Z setiap saat, tetapi Anda hanya mengubah F di sana.
Filip Luchianenco

2
DiffUtil adalah harta terpendam. Terima kasih telah berbagi!
Sileria

22

Alasan yang menyebabkan masalah ini:

  1. Masalah internal di Recycler ketika animasi item diaktifkan
  2. Modifikasi pada data pendaur ulang di utas lain
  3. Memanggil memberitahukan metode dengan cara yang salah

LARUTAN:

----------------- SOLUSI 1 ---------------

  • Menangkap pengecualian (Tidak disarankan terutama karena alasan # 3)

Buat LinearLayoutManager kustom sebagai berikut dan atur ke 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");
                }

            }
        }

Kemudian atur RecyclerVIew Layout Manager sebagai berikut:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUSI 2 ---------------

  • Nonaktifkan animasi item (memperbaiki masalah jika disebabkan oleh alasan # 1):

Sekali lagi, buat Manajer Tata Letak Linear kustom sebagai berikut:

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

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

Kemudian atur RecyclerVIew Layout Manager sebagai berikut:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUSI 3 ---------------

  • Solusi ini memperbaiki masalah jika disebabkan oleh alasan # 3. Anda harus memastikan bahwa Anda menggunakan metode notifikasi dengan cara yang benar. Atau, gunakan DiffUtil untuk menangani perubahan dengan cara yang cerdas, mudah, dan lancar. Menggunakan DiffUtil di Android RecyclerView

----------------- SOLUSI 4 ---------------

  • Untuk alasan # 2, Anda perlu memeriksa semua akses data ke daftar pendaur ulang dan memastikan bahwa tidak ada modifikasi pada utas lainnya.

ini berfungsi dalam skenario saya, saya tidak dapat menggunakan DiffUtil karena saya memiliki komponen khusus untuk pendaur ulang dan adaptor, dan kesalahan terjadi persis dalam skenario tertentu yang diketahui, saya hanya perlu menambalnya tanpa menggunakan animator untuk menghapus item, jadi saya hanya membungkusnya dalam coba & tangkap
RJFares

17

Saya punya masalah serupa.

Masalah dalam kode kesalahan di bawah ini:

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

Larutan:

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

Ini bekerja dengan baik untuk saya! Tidak yakin mengapa kita tidak bisa menggunakan begitu saja newList.size() - 1.
waseefakhtar

15

Menurut masalah ini , masalahnya telah diselesaikan dan kemungkinan dirilis beberapa waktu di dekat awal 2015. Kutipan dari utas yang sama :

Ini secara khusus terkait dengan panggilan notifyDataSetChanged. [...]

Btw, saya sangat menyarankan untuk tidak menggunakan notifyDataSetChanged karena membunuh animasi dan kinerja. Juga untuk kasus ini, menggunakan acara pemberitahuan khusus akan mengatasi masalah ini.

Jika Anda masih mengalami masalah dengan versi terbaru dari pustaka dukungan, saya sarankan meninjau panggilan Anda untuk notifyXXX(khususnya, penggunaan Anda notifyDataSetChanged) di dalam adaptor Anda, untuk memastikan Anda mematuhi RecyclerView.Adapterkontrak (agak rumit / tidak jelas) . Pastikan juga untuk mengeluarkan notifikasi tersebut di utas utama.


16
tidak juga, saya setuju dengan bagian Anda tentang kinerja tetapi notifyDataSetChanged () tidak membunuh animasi, untuk menghidupkan menggunakan notifyDataSetChanged (), a) panggil setHasStableIds (true) pada RecyclerView Anda. Objek objek dan b) menimpa getItemId di dalam Adapter untuk mengembalikan sebuah nilai panjang unik untuk setiap baris dan periksa, animasinya berfungsi
PirateApp

@ PirateApp Anda harus mempertimbangkan untuk membuat komentar Anda sebagai jawaban. Saya sudah mencobanya dan berfungsi baik.
mr5

Tidak benar! Masih mendapatkan laporan dari Google Console tentang masalah ini. Dan perangkat tentu saja adalah Samsung -Samsung Galaxy J3(2017) (j3y17lte), Android 8.0
user25

10

Saya memiliki masalah yang sama. Ini disebabkan karena saya menunda pemberitahuan untuk adaptor tentang penyisipan item.

Tetapi ViewHoldermencoba untuk menggambar ulang beberapa data dalam pandangan itu dan mulai RecyclerViewmenghitung dan menghitung menghitung anak - pada saat itu crash (daftar item dan ukurannya sudah diperbarui, tetapi adaptor belum diberitahu).


8

Ini terjadi ketika Anda menentukan posisi yang salah untuk notifyItemChanged, notifyItemRangeInserted etc.For me:

Sebelum: (Salah)

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

Setelah: (Benar)

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

1
Kenapa notifyItemRangeInserted(initialSize, mChannelItemList.size()-1);dan tidak notifyItemRangeInserted(initialSize, list.size());?
CoolMind

Tidak dapat diuji. Anda bingung initialSizedan listukuran. Jadi, kedua varian Anda salah.
CoolMind

Bagi saya itu bekerja dengan notifyItemRangeInserted(initialSize, list.size()-1);tetapi saya tidak mengerti. Mengapa saya harus mengurangi ukuran yang dimasukkan satu demi satu untuk itemCount?
pleksus

7

alasan lain masalah ini terjadi adalah ketika Anda memanggil metode ini dengan indeks yang salah (indeks yang TIDAK ada masukkan atau hapus di dalamnya)

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInerterted

-notifyItemInserted

periksa parameter indexe untuk metode ini dan pastikan mereka tepat dan benar.


2
Ini masalah saya. Pengecualian terjadi ketika menambahkan tidak ada dalam daftar.
Rasel

6

Bug ini masih belum diperbaiki di 23.1.1, tetapi solusi yang umum adalah menangkap pengecualian.


18
Tangkap di mana, tepatnya? Satu-satunya kode dalam tumpukan jejak adalah kode Android asli.
Howettl

1
Tangkap saja seperti jawaban @saki_M.
Renan Bandeira

Apakah ini benar-benar bekerja untuk Anda meskipun Renan? Sudahkah Anda menguji perbaikan untuk sementara waktu? Bug hanya terjadi sesekali jadi saya hanya akan melihat apakah ini berfungsi seiring waktu.
Simon

Ini sebenarnya bekerja tetapi beberapa pandangan anak di tambang saya tetap tidak konsisten.
david

@david Tidak konsisten tetap apa konsekuensi dari ini?
Sreekanth Karumanaghat

4

Masalah ini disebabkan oleh Data RecyclerView yang dimodifikasi di utas berbeda

Dapat mengkonfirmasi threading sebagai satu masalah dan karena saya mengalami masalah dan RxJava menjadi semakin populer: pastikan bahwa Anda menggunakan .observeOn(AndroidSchedulers.mainThread())setiap kali Anda meneleponnotify[whatever changed]

contoh kode dari adaptor:

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

    [...]

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

Saya berada di utas utama saat menelepon DiffUtil.calculateDiff (diffUtilForecastItemChangesAnlayser (this.mWeatherForecatsItemWithMainDan Cuaca, CuacaForecastItems)). DispatchUpdatesTo (this); lognya jelas onThread: Thread [main, 5, main]
Mathias Seguy Android2ee

4

Dalam kasus saya setiap kali saya menelepon notifyItemRemoved (0), crash. Ternyata saya atur setHasStableIds(true)dan getItemIdsaya baru saja mengembalikan posisi item. Saya akhirnya memperbarui untuk mengembalikan item hashCode()atau id unik yang ditentukan sendiri, yang menyelesaikan masalah.


4

Dalam kasus saya, saya mendapatkan masalah ini karena mendapatkan pembaruan data dari server (saya menggunakan Firebase Firestore) dan sementara set data pertama sedang diproses oleh DiffUtil di latar belakang, serangkaian pembaruan data lainnya datang dan menyebabkan masalah konkurensi. dengan memulai DiffUtil lain.

Singkatnya, jika Anda menggunakan DiffUtil pada utas Latar belakang yang kemudian kembali ke Utas Utama untuk mengirimkan hasilnya ke RecylerView, maka Anda menjalankan peluang untuk mendapatkan kesalahan ini ketika beberapa pembaruan data datang dalam waktu singkat.

Saya menyelesaikan ini dengan mengikuti saran dalam penjelasan yang luar biasa ini: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2

Untuk menjelaskan solusinya adalah dengan mendorong pembaruan sementara yang saat ini berjalan ke Deque. Deque kemudian dapat menjalankan pembaruan yang tertunda setelah yang saat ini selesai, maka menangani semua pembaruan berikutnya tetapi menghindari kesalahan inkonsistensi juga!

Semoga ini bisa membantu karena yang ini membuatku menggaruk kepalaku!


Terima kasih untuk tautannya!
CoolMind

3

Masalah terjadi bagi saya hanya ketika:

Saya membuat Adaptor dengan daftar kosong . Kemudian saya memasukkan item dan menelepon notifyItemRangeInserted.

Larutan:

Saya memecahkan masalah ini dengan membuat Adaptor hanya setelah saya memiliki potongan data pertama dan menginisialisasi dengan itu segera. Potongan selanjutnya bisa dimasukkan dan notifyItemRangeInserteddipanggil tanpa masalah.


Saya tidak berpikir itu alasannya. Saya memiliki banyak adaptor dengan daftar kosong, lalu menambahkan item dengan notifyItemRangeInserted, tetapi tidak pernah memiliki pengecualian ini di sana.
CoolMind

3

Masalah saya adalah bahwa meskipun saya menghapus kedua daftar array yang berisi model data untuk tampilan pendaur ulang, saya tidak memberi tahu adaptor tentang perubahan itu, sehingga memiliki data basi dari model sebelumnya. Yang menyebabkan kebingungan tentang posisi pemegang tampilan. Untuk Memperbaiki ini selalu beri tahu adaptor bahwa set data diubah sebelum memperbarui lagi.


atau cukup beri tahu jika item dihapus sebagai gantinya
Remario

model saya menggunakan referensi wadah itulah sebabnya
Remario

3

Dalam kasus saya, saya mengubah data sebelumnya di dalam utas dengan mRecyclerView.post (Runnable baru ...) dan kemudian lagi mengubah data di utas UI, yang menyebabkan inkonsistensi.


1
saya memiliki situasi yang sama seperti Anda, bagaimana Anda menyelesaikannya? terima kasih
baderkhane

2

Kesalahan dapat disebabkan oleh perubahan Anda yang tidak konsisten dengan apa yang Anda beri tahu. Dalam kasus saya:

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

Apa yang tentu saja harus saya lakukan:

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

2

Dalam kasus saya masalahnya adalah saya menggunakan notifyDataSetChanged ketika jumlah data yang baru dimuat kurang dari data awal. Pendekatan ini membantu saya:

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

Mengapa notifyDataSetChangedtergantung pada data baru? Saya pikir ini akan menyegarkan seluruh daftar.
CoolMind

2

Saya mengalami masalah yang sama.

Aplikasi saya menggunakan komponen Navigasi dengan sebuah fragmen yang mengandung recyclerView saya. Daftar saya ditampilkan dengan baik pertama kali fragmen dimuat ... tetapi setelah menavigasi dan kembali kesalahan ini terjadi.

Ketika menavigasi siklus hidup fragmen hanya melalui onDestroyView dan setelah kembali dimulai pada onCreateView. Namun, adaptor saya diinisialisasi di onCreate fragmen dan tidak menginisialisasi ulang ketika kembali.

Cara mengatasinya adalah menginisialisasi adaptor di onCreateView.

Semoga ini bisa membantu seseorang.


0

Saya mendapatkan kesalahan ini karena saya keliru memanggil metode untuk menghapus baris tertentu dari recyclerview saya beberapa kali. Saya punya metode seperti:

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

Saya tidak sengaja memanggil metode ini tiga kali alih-alih sekali, jadi yang kedua locadalah -1 dan kesalahan diberikan ketika mencoba menghapusnya. Kedua perbaikan itu untuk memastikan metode hanya dipanggil sekali, dan juga untuk menambahkan cek kewarasan seperti ini:

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

0

Saya mendapat masalah yang sama dan saya telah membaca bahwa ini hanya terjadi pada ponsel Samsung ... Tetapi kenyataannya menunjukkan bahwa ini terjadi pada banyak merek.

Setelah pengujian saya menyadari bahwa ini hanya terjadi ketika Anda menggulir cepat RecyclerView dan kemudian Anda kembali dengan tombol kembali atau tombol Naik. Jadi saya masukkan ke dalam tombol Atas dan onBacked potongan di bawah ini:

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

Dengan solusi ini, Anda cukup memuat Arraylist baru ke adaptor dan adaptor baru ke recyclerView dan kemudian Anda menyelesaikan aktivitas.

Semoga ini bisa membantu seseorang


0

Saya mendapat kesalahan ini karena saya memanggil "notifyItemInserted" dua kali karena kesalahan.


0

Dalam kasus saya, saya memiliki lebih dari 5000 item dalam daftar. Masalah saya adalah ketika menggulir tampilan pendaur ulang, kadang-kadang "onBindViewHolder" dipanggil sementara metode "myCustomAddItems" mengubah daftar.

Solusi saya adalah menambahkan "disinkronkan (syncObject) {}" ke semua metode yang mengubah daftar data. Dengan cara ini kapan saja hanya satu metode yang dapat membaca daftar ini.


0

Dalam kasus saya, data adaptor berubah. Dan saya salah menggunakan notifyItemInserted () untuk perubahan ini. Ketika saya menggunakan notifyItemChanged, kesalahan telah hilang.


0

Saya mengalami masalah yang sama ketika saya telah menghapus dan memperbarui item dalam daftar ... Setelah beberapa hari menyelidiki saya pikir saya akhirnya menemukan solusi.

Yang perlu Anda lakukan adalah pertama-tama lakukan semua notifyItemChangeddaftar Anda dan baru kemudian lakukan semua notifyItemRemoved dalam urutan menurun

Saya harap ini akan membantu orang yang mengalami masalah yang sama ...


0

Saya menggunakan kursor jadi saya tidak bisa menggunakan DiffUtils seperti yang diusulkan dalam jawaban populer. Untuk membuatnya berfungsi bagi saya, saya menonaktifkan animasi ketika daftar tidak idle. Ini adalah ekstensi yang memperbaiki masalah ini:

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

Kemudian Anda dapat memperbarui adaptor Anda seperti itu

list.executeSafely {
  adapter.updateICursor(newCursor)
}

0

Jika masalah terjadi setelah multi touch Anda dapat menonaktifkan multi touch dengan

android:splitMotionEvents="false" 

dalam file tata letak.


-1

Jika data Anda banyak berubah, Anda dapat menggunakan

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

atau beberapa item tunggal dalam perubahan set data Anda, dapat Anda gunakan

 mAdapter.notifyItemChanged(pos);

Untuk penggunaan metode terperinci, Anda dapat merujuk dokumen , dengan cara, cobalah untuk tidak langsung menggunakan mAdapter.notifyDataSetChanged().


2
menggunakan notifyItemRangeChangedjuga menghasilkan crash yang sama.
lionelmessi

Itu cocok dengan beberapa situasi. Mungkin Anda memperbarui dataset Anda di utas latar belakang dan utas UI, ini juga akan menyebabkan ketidakkonsistenan. Jika Anda hanya memperbarui dataset di utas UI, itu akan berfungsi.
Arron Cao
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.