IndexOutofBounexception в поисковом адаптере по методу привязки? - PullRequest
2 голосов
/ 26 октября 2019

Я получаю исключение IndexOutofBoundsException из моего метода onBind в поисковом адаптере, как только я начинаю искать / фильтровать результаты моего списка повторного просмотра. Вот исключение:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:0).state:25 android.support.v7.widget.RecyclerView{3247b3d VFED..... ......ID 0,0-1440,5550 #7f090216 app:id/search_guests_recycler_view}, adapter:com.myapp.ui.registervisitor.searchGuests.SearchGuestListAdapter@2433f32, layout:android.support.v7.widget.LinearLayoutManager@68f5483, context:com.myapp.ui.registervisitor.searchGuests.SearchGuestActivity@843713
        at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5923)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
        at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
        at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
        at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
        at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
        at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
        at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
        at android.view.View.measure(View.java:24545)
        at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
        at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
        at android.view.View.measure(View.java:24545)
        at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1414)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.widget.ScrollView.onMeasure(ScrollView.java:452)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
        at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:805)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
        at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:805)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)
        at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
        at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)

Я пробовал пару статей в Интернете, но не повезло. Вот некоторые из статей, которые я пробовал: Метод onBind адаптера RecyclerView и RecyclerView: Обнаружено несоответствие. Неверная позиция элемента

Код моего адаптера для этой проблемы: https://pastebin.com/VxsWWMiS

и соответствующий код активности для фильтрации:

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                mSearchGuestListAdapter.getFilter().filter(query);

                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                mSearchGuestListAdapter.getFilter().filter(newText);
                mSearchGuestListAdapter.notifyDataSetChanged();
                mSearchGuestListAdapter.setFilter(newText);

                if(mSearchGuestListAdapter.getItemCount() == 0){


                    String sourceString = "No match found for <b>" + newText + "</b> ";
                    mNoMatchTextView.setText(Html.fromHtml(sourceString));
                } else {
                    mEmptyRelativeLayout.setVisibility(View.GONE);
                    mRecyclerView.setVisibility(View.VISIBLE);
                }
                return false;
            }
        });

Рад поделитьсядругие детали, если это необходимо. Любые идеи, как решить эту проблему?

Вот весь код моей деятельности: https://pastebin.com/5qDN4yh9

Это класс презентатора, где API называется: https://pastebin.com/YGiPGn8Z

Это класс модели: https://pastebin.com/WCkPFnvU

Это пример json: https://pastebin.com/82R1zBHP

Вот код xml, где называется recyclerview: https://pastebin.com/Z3QxPkSL "search_ghest_recycler_view"

Ответы [ 3 ]

3 голосов
/ 29 октября 2019

В вашем коде есть несколько соображений, которые могут привести к этому несоответствию в вашем адаптере.

Чтобы понять, как работает класс Filter, следует помнить, что согласно официальной документации :

Операции фильтрации выполняются путем вызова фильтра (java.lang.CharSequence) или фильтра (java.lang.CharSequence, android.widget.Filter.FilterListener) выполняются асинхронно . Когда эти методы вызываются, запрос на фильтрацию помещается в очередь запросов и обрабатывается позже. Любой вызов одного из этих методов отменяет любой предыдущий невыполненный запрос на фильтрацию.


1. Используйте один экземпляр Filter

Класс Filter выполняет фоновую задачу, в которой вызывается метод performFiltering(). Чтобы избежать наложения фильтров при наборе текста, вы должны избегать использования нескольких экземпляров Filter. В вашем классе SearchGuestListAdapter:

Неправильно:

@Override
public Filter getFilter() {
    // WRONG: Return new instance of Filter every time getFilter() is called
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {
            // Perform filtering...
            return anything;
        }

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            // Update and Notify adapter...
        }
    };
}

Вместо этого вместо:

private final Filter filter = new Filter() {
    @Override
    protected FilterResults performFiltering(CharSequence charSequence) {
        // Perform filtering...
        return anything;
    }

    @Override
    protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
        // Update and Notify adapter...
    }
};

@Override
public Filter getFilter() {
    // CORRECT: Always use the same Filter instance.
    return this.filter;
}

2. Не следует изменять переменные адаптера в методе executeFiltering ()

performFiltering(), выполняемом в фоновом потоке. Здесь вы можете выполнять долгую работу, читать данные из базы данных и даже делать синхронный запрос веб-службы, если вам это нужно.

Но если вы измените здесь список адаптеров, вы вызовете несоответствие между отображением RecyclerView наэкран и содержимое списка адаптеров. Вместо этого вы должны создать временный список, который вы будете возвращать, используя FilterResults.

Исправьте executeFiltering () со следующей структурой:

@Override
protected FilterResults performFiltering(CharSequence charSequence) {
    // Here is Background Thread, never alter adapter list in this method
    String charString = charSequence.toString();
    List<RegisterGuestList.Guest> filteredList = new ArrayList<>();

    if (charString.isEmpty()) {
        filteredList.addAll(mSearchGuestListResponseList);
    } else {
        ... // fill filteredList as you did previously
    }

    FilterResults filterResults = new FilterResults();
    filterResults.values = filteredList;
    filterResults.count = filteredList.size(); // count is optional
    return filterResults;
}

@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
    // Here is Main Thread, safe to update list and notify adapter
    mSearchGuestListResponseListFiltered = (ArrayList<RegisterGuestList.Guest>) filterResults.values;
    searchText = charSequence.toString();
    notifyDataSetChanged();
}

3. Используйте FilterListener, чтобы получать уведомления о завершении фильтра.

В вашем onQueryTextChange() из SearchGuestActivity вы хотите проверить размер списка адаптеров, чтобы показать или скрыть пустое представление. Поскольку фильтр работает в фоновом потоке, вы должны выполнить вызов фильтра с помощью FilterListener , чтобы проверить пустой список.

Удалите setFilter() метод SearchGuestListAdapter и исправьте onQueryTextChange() SearchGuestActivity с помощьюследующая структура:

@Override
public boolean onQueryTextChange(String newText) {
    mSearchGuestListAdapter.getFilter().filter(newText, new Filter.FilterListener() {
        @Override
        public void onFilterComplete(int count) {
            if (count == 0) {
                // Show empty view...
            } else {
                // Show recyclerView...
            }
        }
    });

    // Don't call notifyDataSetChanged() or setFilter() here!
    // Adaper will notified by publishResults() method
    // mSearchGuestListAdapter.setFilter(newText);
    // mSearchGuestListAdapter.notifyDataSetChanged();

    return false;
}
1 голос
/ 28 октября 2019

Заменить все вхождения mSearchGuestListResponseListв SearchGuestListAdapter#getItemCount(),и в SearchGuestListAdapter#getItemViewType()с mSearchGuestListResponseListFiltered;

0 голосов
/ 28 октября 2019

Трассировка стека выше, похоже, не связана с вашим кодом неявно. И проблема, вызванная внутренней реализацией RecyclerView анимации. Также описано здесь .

1) Возможные обходные пути для этого, просто очистив активный пул ViewHolder s, который может ожидаться при анимации. И мог бы бросить IndexOfBoundException. Вызовите методы ниже, перед любым notifyDataSetChanged.

mRecyclerView.getRecycledViewPool().clear();
mAdapter.notifyDataSetChanged();

2) Убедитесь, что в ваших адаптерах нет методов notifyItemRangeChanged или notifyItemAdded (и подобных). Используйте notifyDataSetChanged. Конечно, вы можете избежать этого, но вам нужно использовать единый сбор и отлаживать правильную позицию каждый раз, чтобы использовать эти функции.

3) И, наконец, как вы можете видеть, все дело в анимации ваших предметов и внутреннем поведении для RecyclerView. Чтобы разрешить его с другой стороны, вы можете переопределить LayoutManager и удалить анимацию. Это обеспечит отсутствие проблем с позициями в вашей реализации.

/**
 * Could be the same for Grid LayoutManager or Linear
 **/
private static class NpaLinearLayoutManager extends LinearLayoutManager {
    /**
     * Disable predictive animations. There is a bug in RecyclerView which causes views that
     * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
     * adapter size has decreased since the ViewHolder was recycled.
     */
    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }
// .....
}
...