Solenya
Solenya

Reputation: 694

Filter RecyclerView with SearchView

I filter the RecyclerView of my app with a SearchView exactly like described in this post:

How to filter a RecyclerView with a SearchView

At the beginning everything looked fine and I could filter the RecyclerView as wanted, but after some playing around, two problems showed up:

  1. If I enter the text too fast into the SearchView, the app crashes with the following log:

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.scherrer.robin.chvote, PID: 28406 java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.ViewGroup$LayoutParams android.view.View.getLayoutParams()' on a null object reference at com.tonicartos.superslim.LayoutManager.getAnchorAtEnd(LayoutManager.java:1038) at com.tonicartos.superslim.LayoutManager.fillNextSectionToEnd(LayoutManager.java:725) at com.tonicartos.superslim.LayoutManager.layoutChildren(LayoutManager.java:1369) at com.tonicartos.superslim.LayoutManager.onLayoutChildren(LayoutManager.java:276) at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3028) at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2906) at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3283) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336) at android.widget.FrameLayout.onLayout(FrameLayout.java:273) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336) at android.widget.FrameLayout.onLayout(FrameLayout.java:273) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.support.design.widget.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:122) at android.support.design.widget.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java:42) at android.support.design.widget.AppBarLayout$ScrollingViewBehavior.onLayoutChild(AppBarLayout.java:1192) at android.support.design.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:814) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336) at android.widget.FrameLayout.onLayout(FrameLayout.java:273) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586) at android.widget.LinearLayout.onLayout(LinearLayout.java:1495) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336) at android.widget.FrameLayout.onLayout(FrameLayout.java:273) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586) at android.widget.LinearLayout.onLayout(LinearLayout.java:1495) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336) at android.widget.FrameLayout.onLayout(FrameLayout.java:273) at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2678) at android.view.View.layout(View.java:16630) at android.view.ViewGroup.layout(ViewGroup.java:5437) at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2171) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1931) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858) at android.view.Choreographer.doCallbacks(Choreographer.java:670) at android.view.Choreographer.doFrame(Choreographer.java:606) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.jav

At first I thought, the error occurs because the filtermethod takes too long and is executed again before it has finished, but with writing into the log, I found out that this isn't causing the issue.

  1. As soon as I filter for something and scroll down, the app crashes with the following log:

FATAL EXCEPTION: main Process: com.scherrer.robin.chvote, PID: 473 java.lang.IndexOutOfBoundsException: Invalid item position 145(145). Item count:20 at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4622) at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4617) at com.tonicartos.superslim.LayoutState.getView(LayoutState.java:48) at com.tonicartos.superslim.LayoutManager.getHeaderOrFirstViewForSection(LayoutManager.java:1242) at com.tonicartos.superslim.LayoutManager.fillToEnd(LayoutManager.java:840) at com.tonicartos.superslim.LayoutManager.fillUntil(LayoutManager.java:908) at com.tonicartos.superslim.LayoutManager.scrollVerticallyBy(LayoutManager.java:361) at android.support.v7.widget.RecyclerView.scrollByInternal(RecyclerView.java:1529) at android.support.v7.widget.RecyclerView.onTouchEvent(RecyclerView.java:2486) at android.view.View.dispatchTouchEvent(View.java:9294) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2547) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2240) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2254) at com.android.internal.policy.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2403) at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1737) at android.app.Activity.dispatchTouchEvent(Activity.java:2765) at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:60) at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:60) at com.android.internal.policy.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2364) at android.view.View.dispatchPointerEvent(View.java:9514) at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4230) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4096) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3787) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3669) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3844) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3669) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642) at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5922) at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5896) at android.view.View

To make the header items in the RecyclerView sticky, I use a library called Super SLiM

The adapter:

public class RfrdAdapter extends RecyclerView.Adapter<RfrdViewHolder> {
private final LayoutInflater mInflater;
private final List<AdapterBaseRow> mModels;

private static final int VIEW_TYPE_HEADER = 0x01;
private static final int VIEW_TYPE_RFRD_OPEN = 0x02;
private static final int VIEW_TYPE_RFRD_PAST = 0x03;

public RfrdAdapter(Context context, List<AdapterBaseRow> models) {
    this.mInflater = LayoutInflater.from(context);
    this.mModels = new ArrayList<>(models);
}

@Override
public RfrdViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View itemView = null;

    switch (viewType) {
        case VIEW_TYPE_HEADER:
            itemView = mInflater.inflate(R.layout.rfrd_list_title, parent, false);
            break;
        case VIEW_TYPE_RFRD_OPEN:
            itemView = mInflater.inflate(R.layout.open_referendum, parent, false);
            break;
        case VIEW_TYPE_RFRD_PAST:
            itemView = mInflater.inflate(R.layout.past_referendum, parent, false);
            break;
        default:
            Log.e("CHVote", "viewType not specified");
            break;
    }

    return new RfrdViewHolder(itemView);
}

@Override
public int getItemViewType(int position) {
    int itemViewType = 0;

    if (mModels.get(position) instanceof AdapterRowHeader) {
        itemViewType = VIEW_TYPE_HEADER;
    } else if (mModels.get(position) instanceof AdapterRowRfrdPast) {
        itemViewType = VIEW_TYPE_RFRD_PAST;
    } else if (mModels.get(position) instanceof AdapterRowRfrdOpen) {
        itemViewType = VIEW_TYPE_RFRD_OPEN;
    }

    return itemViewType;
}

@Override
public void onBindViewHolder(RfrdViewHolder holder, int position) {
    final AdapterBaseRow model = mModels.get(position);
    final View itemView = holder.itemView;
    final LayoutManager.LayoutParams params;

    holder.bind(model);

    params = (LayoutManager.LayoutParams) itemView.getLayoutParams();
    params.setSlm(LinearSLM.ID);
    params.setFirstPosition(model.sectionFirstPosition);
    itemView.setLayoutParams(params);
}

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

public void animateTo(List<AdapterBaseRow> models) {
    applyAndAnimateRemovals(models);
    applyAndAnimateAdditions(models);
    applyAndAnimateMovedItems(models);

    Log.d("tag", "Finshed filtering");
}

private void applyAndAnimateRemovals(List<AdapterBaseRow> newModels) {
    for (int i = mModels.size() - 1; i >= 0; i--) {
        final AdapterBaseRow model = mModels.get(i);
        if (!newModels.contains(model)) {
            removeItem(i);
        }
    }
}

private void applyAndAnimateAdditions(List<AdapterBaseRow> newModels) {
    for (int i = 0, count = newModels.size(); i < count; i++) {
        final AdapterBaseRow model = newModels.get(i);
        if (!mModels.contains(model)) {
            addItem(i, model);
        }
    }
}

private void applyAndAnimateMovedItems(List<AdapterBaseRow> newModels) {
    for (int toPosition = newModels.size() - 1; toPosition >= 0; toPosition--) {
        final AdapterBaseRow model = newModels.get(toPosition);
        final int fromPosition = mModels.indexOf(model);
        if (fromPosition >= 0 && fromPosition != toPosition) {
            moveItem(fromPosition, toPosition);
        }
    }
}

public AdapterBaseRow removeItem(int position) {
    final AdapterBaseRow model = mModels.remove(position);
    notifyItemRemoved(position);
    return model;
}

public void addItem(int position, AdapterBaseRow model) {
    mModels.add(position, model);
    notifyItemInserted(position);
}

public void moveItem(int fromPosition, int toPosition) {
    final AdapterBaseRow model = mModels.remove(fromPosition);
    mModels.add(toPosition, model);
    notifyItemMoved(fromPosition, toPosition);
}

}

The Fragment with the RecyclerView:

public class MainFragment extends Fragment implements SearchView.OnQueryTextListener {

    public static MainFragment newInstance() {
        return new MainFragment();
    }

    private RecyclerView mRecyclerView;
    private RfrdAdapter mAdapter;
    private List<AdapterBaseRow> mModels;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.fragment_main, container, false);

        mRecyclerView = (RecyclerView) view.findViewById(R.id.lvRef);

        return view;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        setHasOptionsMenu(true);

        this.mRecyclerView.setLayoutManager(new LayoutManager(getActivity()));

        AdapterData adapterData = new AdapterData();

        this.mModels = adapterData.getRfrdData();
        mAdapter = new RfrdAdapter(getActivity(), mModels);
        mRecyclerView.setAdapter(mAdapter);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_main, menu);

        final MenuItem item = menu.findItem(R.id.action_search);
        final SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
        searchView.setOnQueryTextListener(this);
    }

    @Override
    public boolean onQueryTextChange(String query) {
        final List<AdapterBaseRow> filteredModelList = filter(mModels, query);

        Log.d("tag","Started filtering");

        mAdapter.animateTo(filteredModelList);
        mRecyclerView.scrollToPosition(0);

        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        return false;
    }

    private List<AdapterBaseRow> filter(List<AdapterBaseRow> models, String query) {
        final List<AdapterBaseRow> filteredModelList = new ArrayList<>();
        final ArrayList<String> searchableTxt = new ArrayList<String>();

        query = query.toLowerCase();

        for (AdapterBaseRow model : models) {
            if (model instanceof AdapterRowHeader) {
                final String searchTxtVotingDateHeader = ((AdapterRowHeader) model).getVotingDateTxt().toLowerCase();
                searchableTxt.add(searchTxtVotingDateHeader);
            } else if (model instanceof AdapterRowRfrdOpen) {
                final String searchTxtTitleRfrdOpen = ((AdapterRowRfrdOpen) model).getOpenRfrd().getTitle().toLowerCase();
                searchableTxt.add(searchTxtTitleRfrdOpen);
                final String searchTxtVotingDateRfrdOpen = ((AdapterRowRfrdOpen) model).getOpenRfrd().getVotingDateTxt().toLowerCase();
                searchableTxt.add(searchTxtVotingDateRfrdOpen);
            } else if (model instanceof AdapterRowRfrdPast) {
                final String searchTxtTitleRfrdPast = ((AdapterRowRfrdPast) model).getPastRfrd().getTitle().toLowerCase();
                searchableTxt.add(searchTxtTitleRfrdPast);
                final String searchTxtVotingDateRfrdPast = ((AdapterRowRfrdPast) model).getPastRfrd().getVotingDateTxt().toLowerCase();
                searchableTxt.add(searchTxtVotingDateRfrdPast);
            }

            for (String sTxt : searchableTxt) {
                if (sTxt.contains(query)) {
                    filteredModelList.add(model);
                    break;
                }
            }

            searchableTxt.clear();
        }
        return filteredModelList;
    }
}

I've looked at many threads here and tried lots of different combinations, but can't seem to be able to get past this problem.

Any help much appreciated.

Upvotes: 2

Views: 1226

Answers (2)

Solenya
Solenya

Reputation: 694

After some tests, I found out the problem is that the superSLiM library needs a customized LayoutManager, but filtering works only with LinearLayoutManager.

Has anybody an idea how to convert the LayoutManager?

Upvotes: 0

Xeros Grey
Xeros Grey

Reputation: 83

I have a recyclerview with superslim and try put a searchview (i use a edittext in the fragment up recyclerview)

I had a lot of problem filtering result, error in headers, ect. Finally I create a non-elegant solution but work fine and is easy to implement:

First, you must have you recyclerview working with your adapter and superslim working (you can see all items stack with yours headers sticky)

Second, my solution is recreate the data (array) with filtered result and rebuild the adapter with some logic:

//event on write in filter field
buscador.addTextChangedListener(new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
                //get the word...
                String text = buscador.getText().toString().toLowerCase(Locale.getDefault());


                if(text.isEmpty()){
                    //if text is empty  we load all results (no filter results)
                    actividades.clear();
                    actividades.addAll(actividadescopia); //actividadescopia is a clone of actividades(data for populate recyclerview, and all results without filtered)
                } else{
                    //if no empty (searching a word)...
                    //we create a array for results finds...
                    ArrayList<ActividadBean> result = new ArrayList<>();
                    text = text.toLowerCase();
                    for(ActividadBean item: actividadescopia){
                        if(item.getTitulo()!=null) {
                            if (item.getTitulo().toLowerCase().contains(text)) {
                            //...add to results if find coincidence
                                result.add(item);
                            }
                        }
                    }
                    //populate array with the results
                    actividades.clear();
                    actividades.addAll(result);
                }

                if(actividades.size()>0) { //if we have results...
                    //...inicialize adapter and set in recyclerview
                    mAdapter = new ListaBusquedaAdapter(getActivity(), mHeaderDisplay, actividades);
                    mRecyclerView.setAdapter(mAdapter);
                    //...and set recyclerview visible for see results (see below)
                    mRecyclerView.setVisibility(View.VISIBLE);
                }else{
                    //..if we haven't results we hide the recyclerview for show that nothing is finding with filtered word
                    mRecyclerView.setVisibility(View.GONE);
                }

            }
            @Override
            public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
                // TODO Auto-generated method stub
            }
            @Override
            public void afterTextChanged(Editable arg0) {
                // TODO Auto-generated method stub
            }
        });

I hope it helps you (Sorry im no english native)

Upvotes: 1

Related Questions