Загрузка данных в LiveData с наблюдением за представлением Recycler: обнаружена несогласованность. Недопустимое положение адаптера держателя представления

Я работаю над примером приложения для социальных сетей с фотографиями и альбомами. Использование компонентов архитектуры Android Room, LiveData и ViewModel с дооснащением. Вокруг Room есть слой репозитория, который обрабатывает операции с данными.

RecyclerView -> ViewModel -> Repository -> Room <- Retrofit

@Entity(tableName = "photos",
        foreignKeys = {@ForeignKey(entity = AlbumModel.class, parentColumns = "id", childColumns = "album_id")},
        indices = {@Index(name = "index_album", value = "album_id")})
public class PhotoModel {

    @PrimaryKey
    private int id;

    @ColumnInfo(name = "album_id")
    private int albumId;

    private String title;
    private String url;
    private String thumbnail_url;
}

ViewModel
    public class PhotoViewModel extends AndroidViewModel {
        private LiveData<List<PhotoModel>> photos;
        private PhotoRepository photoRepository;

        public LiveData<List<PhotoModel>> getAlbumPhotos(int album_id) {
            if (photos == null) {
                photos = photoRepository.getAlbumPhotos(album_id);
            }
            return photos;
        }

    }

PhotoRepository
public class PhotoRepository implements PhotosInterface {
    private LiveData<List<PhotoModel>> photos;
    private PhotoDao photoDao;
    private AlbumRepository albumRepository;

    public LiveData<List<PhotoModel>> getAlbumPhotos(int album_id) {
        PhotoController.getAlbumPhotos(this, album_id);
        if (photos == null) {
            photos = photoDao.getAlbumPhotos(album_id);
        }
        return photos;
    }
}

@Dao
public abstract class PhotoDao {

    @Query("SELECT * FROM photos WHERE album_id=:album_id ORDER BY id DESC")
    public abstract LiveData<List<PhotoModel>> getAlbumPhotos(int album_id);
}

Вот где проблема: Когда фотографии загружаются с сервера, он проверяет альбом, поскольку album_id является внешним ключом в модели фотографии. Это происходит в AsycTask. Таким образом, фотографии вставляются не по порядку, что приводит к тому, что listadapter обновляет список и пользовательский интерфейс, что вызывает ошибку, указанную ниже. Это та же проблема с другими представлениями Recycler в приложении, основанными на той же архитектуре.

//Inserting photos through Photos Dao in Retro callback
    private  void InsertPhotoWithAlbum(PhotoDao photoDao, AlbumRepository albumRepository, PhotoModel photo) {
            if (photoDao.getPhotoObjectById(photo.getId()) == null ) {
                if (albumRepository.getAlbumObjectById(photo.getAlbumId()) != null) {
                    photoDao.insertPhoto(photo);
                } else {
                    albumRepository.fetchAlbum(photo.getAlbumId());
                    photoDao.insertPhoto(photo);
                }
            }
        }

Теперь альбом содержит фотографии и перечислен в RecyclerView с использованием StaggeredGridLayoutManager. Ниже представлен ListAdapter для RecyclerView.

public class CustomListAdapter extends ListAdapter<PhotoModel, CustomListAdapter.PhotoViewHolder> implements AlbumDetailActivity.RecyclerViewOnClickListener{
        private List<PhotoModel> photos;
        private Context mContext;

        public CustomListAdapter(@NonNull DiffUtil.ItemCallback<PhotoModel> diffCallback, Context context) {
            super(diffCallback);
            mContext = context;
            PhotoViewModel photoViewModel = ViewModelProviders.of(AlbumDetailActivity.this).get(PhotoViewModel.class);
            photoViewModel.getAlbumPhotos(album_id).observe(AlbumDetailActivity.this, new Observer<List<PhotoModel>>() {
                @Override
                public void onChanged(@Nullable List<PhotoModel> photoModels) {
                    submitList(photoModels);
                    photos = photoModels;
                }
            });
//            photos = photoViewModel.getAlbumPhotosObjects(album_id);
        }

        @NonNull
        @Override
        public PhotoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View itemView = LayoutInflater.from(mContext).inflate(R.layout.listitem_photos_list, null);
            PhotoViewHolder viewHolder = new PhotoViewHolder(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                }
            });
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(@NonNull PhotoViewHolder holder, int position) {
            PhotoModel photo = photos.get(position);
            holder.tv_title.setText(photo.getTitle());
            Picasso.get().load(photo.getThumbnail_url()).into(holder.iv_photo_thumbnail);
        }

        public class PhotoViewHolder extends RecyclerView.ViewHolder {
            TextView tv_title;
            ImageView iv_photo_thumbnail;

            public PhotoViewHolder(View itemView) {
                super(itemView);
                tv_title = itemView.findViewById(R.id.tv_photo_title);
                iv_photo_thumbnail = itemView.findViewById(R.id.iv_photo_thumbnail);
            }

        }

        @Override
        public int getItemCount() {
            return photos == null ? 0 : photos.size();
        }

        @Override
        public void onItemClick(PhotoModel photo) {
            Toast.makeText(mContext, "Clicked on : " + photo.getId() + ":" + photo.getTitle() , Toast.LENGTH_SHORT).show();
        }
    }

При загрузке фотографий приложение вылетает со следующей ошибкой.

E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: assess.talview.com.yalview_yasma, PID: 14830
                  java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{b4cfec1 position=42 id=-1, oldPos=39, pLpos:39 scrap [attachedScrap] tmpDetached not recyclable(1) no parent} android.support.v7.widget.RecyclerView{a26494c VFED..... .F....ID 0,0-1080,1731 #7f08007b app:id/recyclerview_photos}, adapter:assess.talview.com.yalview_yasma.album.AlbumDetailActivity$CustomListAdapter@6a61dfa, layout:android.support.v7.widget.StaggeredGridLayoutManager@b391fab, context:assess.talview.com.yalview_yasma.album.AlbumDetailActivity@520aa8f
                      at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5610)
                      at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5792)
                      at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5752)
                      at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5748)
                      at android.support.v7.widget.LayoutState.next(LayoutState.java:100)
                      at android.support.v7.widget.StaggeredGridLayoutManager.fill(StaggeredGridLayoutManager.java:1611)
                      at android.support.v7.widget.StaggeredGridLayoutManager.onLayoutChildren(StaggeredGridLayoutManager.java:685)
                      at android.support.v7.widget.StaggeredGridLayoutManager.onLayoutChildren(StaggeredGridLayoutManager.java:607)
                      at android.support.v7.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:3763)
                      at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3527)
                      at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:4082)
                      at android.view.View.layout(View.java:19659)
                      at android.view.ViewGroup.layout(ViewGroup.java:6075)
                      at android.support.design.widget.CoordinatorLayout.layoutChild(CoordinatorLayout.java:1191)
                      at android.support.design.widget.CoordinatorLayout.onLayoutChild(CoordinatorLayout.java:876)
                      at android.support.design.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:895)
                      at android.view.View.layout(View.java:19659)
                      at android.view.ViewGroup.layout(ViewGroup.java:6075)
                      at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
                      at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
                      at android.view.View.layout(View.java:19659)
                      at android.view.ViewGroup.layout(ViewGroup.java:6075)
                      at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
                      at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
                      at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
                      at android.view.View.layout(View.java:19659)
                      at android.view.ViewGroup.layout(ViewGroup.java:6075)
                      at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
                      at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
                      at android.view.View.layout(View.java:19659)
                      at android.view.ViewGroup.layout(ViewGroup.java:6075)
                      at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
                      at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
                      at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
                      at android.view.View.layout(View.java:19659)
                      at android.view.ViewGroup.layout(ViewGroup.java:6075)
                      at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
                      at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
                      at com.android.internal.policy.DecorView.onLayout(DecorView.java:761)
                      at android.view.View.layout(View.java:19659)
                      at android.view.ViewGroup.layout(ViewGroup.java:6075)
                      at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2496)
                      at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2212)
                      at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1392)
                      at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6752)
                      at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
                      at android.view.Choreographer.doCallbacks(Choreographer.java:723)
E/AndroidRuntime:     at android.view.Choreographer.doFrame(Choreographer.java:658)
                      at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
                      at android.os.Handler.handleCallback(Handler.java:790)
                      at android.os.Handler.dispatchMessage(Handler.java:99)
                      at android.os.Looper.loop(Looper.java:164)
                      at android.app.ActivityThread.main(ActivityThread.java:6494)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Поиск ошибки предложил submitting копию нового списка вместо существующего, что не имело значения.

Как решить эту проблему? Есть ли обходной путь или другой подход для решения этой проблемы, с LiveData или без него?

4
0
797
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Для тех, кто сталкивается с той же проблемой:

Проблема заключалась в том, что когда список данных увеличивался, позиция адаптера данных была больше, чем размер данных, поскольку getItemCount был переопределен и не обновлен. Таким образом, это условие не выполнялось в методе RecyclerView.Recycle.validateViewHolderForOffsetPosition.

Решением было использовать not override метод getItemCount адаптера списка и позволить Recycler View обрабатывать размер данных. И после этого приложение работало безупречно.

У меня такая же проблема. Но я не отменял метод getItemCount.

Arun 29.11.2018 13:30

Я использую ListAdapter с DIFF_CALLBACK и Filterable, список не обновлялся при вызове submitList, приложение вылетало, я удаляю искомые ключевые слова в EditText, потратил на это пару часов, ваше решение работает, спасибо!

li2 30.03.2019 11:04

Другие вопросы по теме