简体   繁体   中英

My RecyclerView keeps getting reset after filtering

I have a working RecyclerView and I need to implement a search bar. I managed to create a search bar out of EditText and TextWatcher, but I have the following problem:

Normally, my list passes data based on the position of current item, and when I filter the list, the positions get of course screwed up.

I followed some guides and I found this workaround:

private void filter(String text){
    ArrayList<Item> filteredList = new ArrayList<>();

    for (Item item : mList ) {
        if(item.getName().toLowerCase().contains(text.toLowerCase())) {
            filteredList.add(item);
        }
    }
    mListAdapter.filterList(filteredList);

    //when the app is done filtering, it clears the existing ArrayList and it adds just the filtered Items

    mList.clear();
    mList.addAll(filteredList);
}

The problem with this approach is that of course when mList.clear() happens, I lose all my data and only the filtered ones remain. Then when I remove the text from the search bar all I have is an empty RecyclerView.

I thought about trying to reset the list to it's original state when the EditText is null, but I don't know how to do that and it's not ideal.

Can anyone think of a way to fix this? I'm getting really desperate :D

My RecyclerView class:

public class ListFragment extends Fragment {

static final String EXTRA_NAME = "monumentname";
static final String EXTRA_NUMBER = "monumentnumber";
static final String EXTRA_REGION = "monumentregion";
static final String EXTRA_REGION2 = "monumentregion2";
static final String EXTRA_TOWN = "monumenttown";
static final String EXTRA_DESCRIPTION = "monumentdescription";
static final String EXTRA_WEB = "web";

private ListAdapter mListAdapter;
private ArrayList<Item> mList;
private RequestQueue mRequestQueue;
private Context mContext;
private InternetCheck internetCheck = new InternetCheck();


@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable final Bundle savedInstanceState) {
    View rootview = inflater.inflate(R.layout.fragment_list, container, false);

    RecyclerView mRecyclerView = rootview.findViewById(R.id.recycler_view);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    Button syncData = rootview.findViewById(R.id.sync_button);
    final ProgressBar progressBar = rootview.findViewById(R.id.progressbar);
    EditText editText = rootview.findViewById(R.id.edittext);

    editText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void afterTextChanged(Editable s) {
            filter(s.toString());
        }
    });

..........some other irrelevant stuff here


    mListAdapter.setOnItemClickListener(new ListAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(int position) {
            Intent detailIntent = new Intent(getActivity(), SingleItem.class);
            Item clickedItem = mList.get(position);

            detailIntent.putExtra(EXTRA_NAME, clickedItem.getName());
            detailIntent.putExtra(EXTRA_NUMBER, clickedItem.getNumber());
            detailIntent.putExtra(EXTRA_REGION, clickedItem.getRegion());
            detailIntent.putExtra(EXTRA_REGION2, clickedItem.getRegion2());
            detailIntent.putExtra(EXTRA_TOWN, clickedItem.getTown());
            detailIntent.putExtra(EXTRA_DESCRIPTION, clickedItem.getDescription());
            detailIntent.putExtra(EXTRA_WEB, clickedItem.getWeb());

            startActivity(detailIntent);
        }
    });


    return rootview;
}

.......some onCreateView and stuff like that here

public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    loadData();
    mListAdapter = new ListAdapter(mContext, mList);
    mRequestQueue = Volley.newRequestQueue(mContext);

}

private void parseJSON() {

    JsonObjectRequest request = new JsonObjectRequest("http://192.168.0.105/sestavsisvujsvetweb/api/seznammagnetek", null, new Response.Listener<JSONObject>() {

.....parsing my JSON here



    mRequestQueue.add(request);

}

private CharSequence removeHtmlFrom(String html) {
    return new HtmlCleaner().clean(html).getText();
}

@Override
public void onStop() {
    super.onStop();
    saveData();
}


private void saveData() {
    SharedPreferences sharedPreferences = getContext().getSharedPreferences("shared preferences", MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedPreferences.edit();
    Gson gson = new Gson();
    String json = gson.toJson(mList);
    editor.putString("seznam magnetek", json);
    editor.apply();
}

private void loadData() {
    SharedPreferences sharedPreferences = getContext().getSharedPreferences("shared preferences", MODE_PRIVATE);
    Gson gson = new Gson();
    String json = sharedPreferences.getString("seznam magnetek", null);
    Type type = new TypeToken<ArrayList<Item>>() {
    }.getType();
    mList = gson.fromJson(json, type);

    if (mList == null || mList.isEmpty()) {
        Toast.makeText(getContext(), "Seznam magnetek je prázdný. Aktualizujte prosím data z databáze.", Toast.LENGTH_SHORT).show();
        mList = new ArrayList<>();
    }
}

private void filter(String text) {
    ArrayList<Item> filteredList = new ArrayList<>();

    for (Item item : mList) {
        if (item.getName().toLowerCase().contains(text.toLowerCase())) {
            filteredList.add(item);
        }
    }
    mListAdapter.filterList(filteredList);
    mList.clear();
    mList.addAll(filteredList);
}

}

You have 2 potential problems here:

  1. After the filteredList is passed to the mListAdapter, you have to call adapter.notifyDataSetChanged notifyDataSetChanged to invalidate the list view.
  2. Besides, you don't need to clear the mList after you add the filtered list to adapter. As loadData is only called once, if you clear this the first time, the list only have the filtered item left. Even if you remove the filter string, the item will not added back to the mList .

Please try add the following:

private void filter(String text){
    ArrayList<Item> filteredList = new ArrayList<>();

    for (Item item : mList ) {
        if(item.getName().toLowerCase().contains(text.toLowerCase())) {
            filteredList.add(item);
        }
    }
    mListAdapter.filterList(filteredList);
    mListAdapter.notifyDataSetChanged();
}

In your RecyclerView Adapter class add a ValueFilter and an arraylist that holds the previous data.

private ValueFilter valueFilter;
private ArrayList<Item> mListFull;  // this arraylist contains previous data.
private ArrayList<Item> mList;


public Filter getFilter() {
        if (valueFilter == null) {
            valueFilter = new ValueFilter();
        }
        return valueFilter;
}

ValueFilter will show cannot resolve symbol 'ValueFilter' , so create a class called ValueFilter .

Create the class ValueFilter

private class ValueFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {

        String str = constraint.toString().toUpperCase();
        Log.e("constraint", str);
        FilterResults results = new FilterResults();

        if (constraint.length() > 0) {
            ArrayList<Item> filterList = new ArrayList<>();
            for (int i = 0; i < mListFull.size(); i++) {
                if ((mListFull.get(i).getName().toUpperCase())
                        .contains(constraint.toString().toUpperCase())) {
                    Item item = mListFull.get(i);
                    filterList.add(item);
                }
            }
            results.count = filterList.size();
            results.values = filterList;
        } else {
            results.count = mListFull.size();
            results.values = mListFull;
        }
        return results;
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        mList = (ArrayList<Item>) results.values;
        notifyDataSetChanged();
    }
}

Now in your EditText TextChangedListener onTextChanged

 editText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            youradapter.getFilter().filter(charSequence.toString());  //Add this (change youradapter with the name of your recyclerview adapter.)
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });

You made a silly mistake. You are clearing and adding new values in your real list also. You should not do that.

  • You should only parse a new list [ filterlist ] which you got in the filter.
  • And remove to call filter(s.toString()) from afterTextChanged and add-in onTextChanged

Example :-

Step 1 : change call filter in class

editText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        filter(s.toString());
    }

    @Override
    public void afterTextChanged(Editable s) {
//            filter(s.toString());
    }
});

Step 2 : remove below the line in filter function.

mList.clear();
mList.addAll(filteredList);

Step 3 : and in your Adapter class :-

public void filterlist(List<Item> name) {
    this.Listname = name;
    notifyDataSetChanged();
}

To get a full understanding of Search in Recycleview to refer below link:-

how can fillter the recyclerView

Hi Etoile,

Our Sample Problem:

Let say we have a picture frame, all we want to do is erase part of it for a period of time, after which we want the full frame to show up again.

Posible Solution:

We can save instance of the full Frame somewhere let say A = instance of Frame then we can say B = A. So, we can use B as our main Frame and A as the backup frame. After we modify B frame by erasing some part for a period of time, then we can easily return back to our previous state by saying if we don't want to modify again, then let B = A. since our A still maintain its state.

Sample Implementation From your code (Modified though)

private ArrayList<Item> mList; //Aka A from our solution above
private ArrayList<Item> filteredList = new ArrayList<>();// Aka B from our solution above

public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mListAdapter = new ListAdapter(mContext, filteredList);
    loadData();

editText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2{
            filter(s.toString()); // I feel like it will be faster filtering from here
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    });
}

/* Here, we load the lists of items and notify our adapter of the changes, the last time in this function does the nofity */
private void loadData() {
    SharedPreferences sharedPreferences = getContext().getSharedPreferences("shared preferences", MODE_PRIVATE);
    Gson gson = new Gson();
    String json = sharedPreferences.getString("seznam magnetek", null);
    mList = gson.fromJson(json, new TypeToken<ArrayList<Item>>(){}.getType(););

    if (mList == null || mList.isEmpty()) {
        Toast.makeText(getContext(), "Seznam magnetek je prázdný. Aktualizujte prosím data z databáze.", Toast.LENGTH_SHORT).show();
        mList = new ArrayList<>();
    }

    filteredList = mList;
    mListAdapter.notifyDataSetChanged();
}

//So, if nothing is typed in the EditText, we simply want to reset back to the //previous state else do as appropriate i.e modify filteredList and notify adapter
private void filter(String text){
    if(text.isEmpty()){
      filteredList = mList;
      mListAdapter.notifyDataSetChanged();
    }else{
      filteredList.clear();

      for (Item item : mList ) {
        if(item.getName().toLowerCase().startsWith(text.toLowerCase())) {
            filteredList.add(item); //Aka B from our solution above
        }
      }

//we have cleared and modified filteredList, all we need to do if notify adapter.
      mListAdapter.notifyDataSetChanged();
    }
}

Please file a bug if any and we can make corrections. Happy coding.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM