在RecyclerView中实现分页时获取IndexOutOfBoundsException错误

时间:2017-12-18 09:58:37

标签: android android-recyclerview pagination retrofit2 crash-reports

我一直非常细致地遵循this指南在我的RecyclerView中实施分页。唯一的区别是我使用Retrofit从API中检索数据。

当我打开我的活动时,它会在一段时间后停止,当我检查Logcat时,它会说:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{31a22aa position=4 id=-1, oldPos=2, ...这就是我的RecyclerView适配器的样子:

public class CuratedSectionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static int VIEW_TYPE_HEADER = 0;
    private static int VIEW_TYPE_ITEM = 1;
    private static int VIEW_TYPE_LOADING = 2;

    private RecyclerView mRecyclerView;
    private OnLoadMoreListener mOnLoadMoreListener;

    private boolean isLoading;
    private int visibleThreshold = 2;
    private int lastVisibleItem, totalItemCount;

    private List<Object> itemList;

    public CuratedSectionAdapter(List<Object> itemList, RecyclerView recyclerView, LinearLayoutManager layoutManager) {
        this.itemList = itemList;
        this.mRecyclerView = recyclerView;

        final LinearLayoutManager linearLayoutManager = layoutManager;
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();

                if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    if (mOnLoadMoreListener != null) {
                        mOnLoadMoreListener.onLoadMore();
                    }
                    isLoading = true;
                }
            }
        });
    }

    public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
        this.mOnLoadMoreListener = mOnLoadMoreListener;
    }

    private class ItemViewHolder extends RecyclerView.ViewHolder {

        RecyclerView itemRecyclerView;
        CuratedSectionNestedAdapter nestedAdapter;
        LinearLayoutManager layoutManager;

        ItemViewHolder(View view) {
            super(view);
            itemRecyclerView = view.findViewById(R.id.recyclerView_nested);
            layoutManager = new LinearLayoutManager(view.getContext(), LinearLayoutManager.HORIZONTAL, false);
        }
    }

    private class HeaderViewHolder extends RecyclerView.ViewHolder {
        TextView textViewHeader;
        Typeface montserratMedium = Typeface.createFromAsset(getApplicationContext().getAssets(), "fonts/Montserrat-Medium.ttf");

        HeaderViewHolder(View view) {
            super(view);
            textViewHeader = view.findViewById(R.id.textView_header);
            textViewHeader.setTypeface(montserratMedium);
        }
    }

    private class LoadingViewHolder extends RecyclerView.ViewHolder {
        ProgressBar progressBar;

        LoadingViewHolder(View itemView) {
            super(itemView);
            progressBar = itemView.findViewById(R.id.progress_bar);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        if (holder.getItemViewType() == VIEW_TYPE_HEADER) {
            HeaderViewHolder viewHolder = (HeaderViewHolder) holder;
            CuratedSectionHeader header = (CuratedSectionHeader) itemList.get(position);

            viewHolder.textViewHeader.setText(header.getHeaderName());
        } else if (holder.getItemViewType() == VIEW_TYPE_ITEM){
            ItemViewHolder viewHolder = (ItemViewHolder) holder;
            List<CuratedSectionItem> items = (List<CuratedSectionItem>) itemList.get(position);

            if (viewHolder.nestedAdapter != null) {
                viewHolder.nestedAdapter.setItems(items);
            } else {
                viewHolder.nestedAdapter = new CuratedSectionNestedAdapter(items);
                viewHolder.itemRecyclerView.setLayoutManager(viewHolder.layoutManager);
                viewHolder.itemRecyclerView.setAdapter(viewHolder.nestedAdapter);
            }
        } else if (holder.getItemViewType() == VIEW_TYPE_LOADING){
            LoadingViewHolder viewHolder = (LoadingViewHolder) holder;
            viewHolder.progressBar.setIndeterminate(true);
        }

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_HEADER) {
            return new HeaderViewHolder(LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.main_explore_header_row, parent, false));
        } else if (viewType == VIEW_TYPE_ITEM) {
            return new ItemViewHolder(LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.main_explore_row, parent, false));
        } else if (viewType == VIEW_TYPE_LOADING) {
            return new LoadingViewHolder(LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.layout_loading_item, parent, false));
        }

        throw new RuntimeException("Adapter " + viewType + "not found");
    }

    @Override
    public int getItemCount() {
        return itemList.size();
    }

    @Override
    public int getItemViewType(int position) {
        if(isHeader(position)) {
            return VIEW_TYPE_HEADER;
        } else if(isLoading(position)) {
            return VIEW_TYPE_LOADING;
        } else {
            return VIEW_TYPE_ITEM;
        }
    }

    public boolean isHeader(int position) {
        return itemList.get(position) instanceof CuratedSectionHeader;
    }

    public boolean isLoading(int position) {
        return position > itemList.size();
    }

    public void setLoaded() {
        isLoading = false;
    }
}

这可能与我构建LoadingViewHolder的方式有关,但我认为我正确地做了那个。另一方面,这就是活动的样子:

public class ExploreFeedActivity extends Activity {

    private RecyclerView recyclerView;
    private CuratedSectionAdapter curatedSectionAdapter;
    private LinearLayoutManager layoutManager;
    private Sections curated;

    int mStart = 0;
    int mLimit = 2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_explore);

        curated = new Sections();
        curated.loadSections(mStart, mLimit);

        recyclerView = findViewById(R.id.recycler_view_main);

        layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

    }

    private class Sections {
        ArrayList<CuratedSection> sections = new ArrayList<>();
        public Thread[] thread;

        private Sections() {}

        public void setSections(ArrayList<CuratedSection> sections) {
            this.sections = sections;
        }

        public void setSectionStories(String sectionId, List<CuratedSectionItem> stories) {
            for(CuratedSection s : sections){
                if(s.id != null && s.id.equals(sectionId)) {
                    s.stories = stories;
                }
            }
        }

        public void loadStories(String sessionKey) {
            thread = new Thread[sections.size()];
            for( int s = 0; s < sections.size(); s++) {
                thread[s] = new Thread(new LoadStories(sessionKey, sections.get(s)));
                thread[s].start();
            }
            for( int f = 0; f < sections.size(); f++) {
                try {
                    thread[f].join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            curatedSectionAdapter = new CuratedSectionAdapter(this.getAdapterInfo(), recyclerView, layoutManager);
            recyclerView.setLayoutManager(layoutManager);
            recyclerView.setAdapter(curatedSectionAdapter);
            curatedSectionAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
                @Override
                public void onLoadMore() {
                    Log.e("haint", "Load More");
                    curated.sections.add(null);
                    curatedSectionAdapter.notifyItemInserted(curated.sections.size() - 1);

                    // Load more data for recyclerview
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            Log.e("haint", "Load Further");

                            // Remove loading item
                            curated.sections.remove(curated.sections.size() - 1);
                            curatedSectionAdapter.notifyItemRemoved(curated.sections.size());

                            // Load data
                            curated.loadSections(2, 2);
                            curatedSectionAdapter.notifyDataSetChanged();
                            curatedSectionAdapter.setLoaded();
                        }
                    }, 5000);
                }
            });
        }

        public void loadSections(int start, int limit) {
            swipeRefreshLayout.setRefreshing(false);
            LoadSections load = new LoadSections(start, limit);
            load.run();
        }

        public List<Object> getAdapterInfo() {
            List<Object> list = new ArrayList<>();

            for (int i = 0; i < sections.size(); i++) {
                CuratedSection section = sections.get(i);
                CuratedSectionHeader header = new CuratedSectionHeader();
                header.setHeaderName(section.header);

                list.add(header);
                list.add(section.stories);
            }

            return list;
        }
    }


    private class LoadSections implements Runnable {

        int start, limit;

        LoadSections(int start, int limit) {
            this.start = start;
            this.limit = limit;
        }

        @Override
        public void run() {
            SharedPreferences prefs = getSharedPreferences("user_session", MODE_PRIVATE);
            final String sessionKey = prefs.getString("session_key", null);

            Call<JsonArray> call;
            call = TravelersApi.endpoint().getCuratedSections(sessionKey);

            call.enqueue(new Callback<JsonArray>() {
                @Override
                public void onResponse(Call<JsonArray> call, Response<JsonArray> response) {
                    if(response.code() != 200) {
                        Toast.makeText(getApplicationContext(), "Cannot load page as of the moment.", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    JsonArray rawSections = response.body();
                    if(rawSections.size() == 0) {
                        //TODO: show placeholder
                        return;
                    }
                    for(int i = start; i < start+limit; i++) {
                        JsonObject jSection = rawSections.get(i).getAsJsonObject();
                        final CuratedSection section = new CuratedSection();
                        section.id = jSection.get("id").getAsString();
                        section.header = jSection.get("section_header").getAsString();
                        section.isShown = jSection.get("is_shown").getAsBoolean();
                        section.stories = new ArrayList<>();

                        curated.sections.add(section);
                    }
                    curated.setSections(curated.sections);
                    curated.loadStories(sessionKey);
                }

                @Override
                public void onFailure(Call<JsonArray> call, Throwable t) {
                    Log.d("ERROR!", t.toString());
                    t.printStackTrace();
                }
            });
        }
    }

    private class LoadStories implements Runnable {

        String sessionKey;
        CuratedSection section;

        LoadStories(String sessionKey, CuratedSection section) {
            this.sessionKey = sessionKey;
            this.section = section;
        }

        @Override
        public void run() {
            Call<JsonArray> subCall;
            subCall = TravelersApi.endpoint().getCuratedSectionTopics(sessionKey, section.id);

            try {
                Response<JsonArray> response = subCall.execute();
                if(response.code() != 200) {
                    Toast.makeText(getApplicationContext(), "Cannot load page as of the moment.", Toast.LENGTH_SHORT).show();
                    return;
                }
                JsonArray rawStories = response.body();
                if(rawStories.size() == 0) {
                    //TODO: show placeholder
                    return;
                }
                ArrayList<CuratedSectionItem> stories = new ArrayList<>();
                for(int i = 0; i < rawStories.size(); i++) {
                    JsonObject jStories = rawStories.get(i).getAsJsonObject();
                    JSONObject temp = new JSONObject(jStories.toString());
                    JsonObject author = jStories.get("author").getAsJsonObject();
                    CuratedSectionItem story = new CuratedSectionItem();
                    story.title = jStories.get("title").getAsString();
                    story.avatar = author.get("profile_photo").getAsString();
                    stories.add(story);
                }
                section.stories = stories;
            } catch (IOException e) {
                Log.d("ERROR!", e.toString());
                e.printStackTrace();
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

值得注意的是,我的RecyclerView适配器curatedSectionAdapter已在私有类loadStories内的方法Sections中实例化。我已经尝试将该部分移动到onCreate,但随后活动不会显示任何内容。我还怀疑我在LoadSections的回调中构造for循环的方式与崩溃有关。

0 个答案:

没有答案