How to Implement Filterable in Realmrecyclerviewadapter

how to implement filterable in RealmRecyclerViewAdapter

Move the filtering to publishResults and use the UI thread Realm's queries to evaluate the new results.

private class AirportAdapter
extends RealmRecyclerViewAdapter<AirportR, RecyclerView.ViewHolder>
implements Filterable {
Realm realm;

public AirportAdapter(Context context, Realm realm, OrderedRealmCollection<AirportR> airports) {
super(context, airports, true);
this.realm = realm;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.airport_show, parent, false);
AirportClass holder = new AirportClass(view);
return holder;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
AirportR airportR = getData().get(position);

AirportClass mHolder = (AirportClass) holder;
mHolder.bind(airportR);
}

public void filterResults(String text) {
text = text == null ? null : text.toLowerCase().trim();
RealmQuery<AirportR> query = realm.where(AirportR.class);
if(!(text == null || "".equals(text))) {
query.contains("fieldToQueryBy", text, Case.INSENSITIVE) // TODO: change field
}
updateData(query.findAllAsync());
}

public Filter getFilter() {
AirportFilter filter = new AirportFilter(this);
return filter;
}

private class AirportFilter
extends Filter {
private final AirportAdapter adapter;

private AirportFilter(AirportAdapter adapter) {
super();
this.adapter = adapter;
}

@Override
protected FilterResults performFiltering(CharSequence constraint) {
return new FilterResults();
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
adapter.filterResults(constraint.toString());
}
}

private class AirportClass
extends RecyclerView.ViewHolder {
TextView name, country;
ImageView image;

public AirportClass(View itemView) {
super(itemView);

name = (TextView) itemView.findViewById(R.id.name);
country = (TextView) itemView.findViewById(R.id.country);
image = (ImageView) itemView.findViewById(R.id.imageView);
}

public void bind(AirportR airportR) {
country.setText(airportR.getIsoCountry());
name.setText(airportR.getName());
}
}
}

Filtering in RealmRecyclerViewAdapter does not hide excluded elements

I don't really know if this is the way to go for this problem, but it's now behaving as expected so I'll share the solution here.

Inside the HabitCardAdapter I added another OrderedRealmCollection<Habit> member, called filteredList, while list holds the whole data. In the costructor both of filteredList and list are tied to the data passed to the constructor, but while filteredList will be modified by the query, list will not (probably putting it to final is the best practice). Then everything in the Adapter will now reference to filteredList instead of list, and when the SearchView is selected and the query is up, filteredList will get the data, and then updateData(filteredList) will be called.

Here is what I changed:

public class HabitCardAdapter extends RealmRecyclerViewAdapter<Habit, HabitCardAdapter.ViewHolder> implements Filterable {
Context ct;
OrderedRealmCollection<Habit> list;
OrderedRealmCollection<Habit> filteredList;
Realm mRealm;

...
}
public HabitCardAdapter(@Nullable OrderedRealmCollection<Habit> data, Context context, Realm realm) {
super(data, true, true);
ct = context;
list = data;
filteredList = data;
mRealm = realm;
setHasStableIds(true);
}

Probably the error was here in getItemCount(), when the filteredListsize was smaller than the list one, but since I didn't have any reference to filteredList, I didn't have any way to change that size, and so the Adapter would continue to show - for example - 6 views while I was querying for 3. Having it as a properly class member it let me make this:

@Override
public int getItemCount() {
return this.filteredList.size();
}
public void filterResults(String text) {
text = text == null ? null : text.toLowerCase().trim();
if (text == null || "".equals(text)) {
filteredList = list;
} else {
filteredList = mRealm.where(Habit.class).beginsWith("title", text, Case.INSENSITIVE).sort("id").findAll();
}
updateData(filteredList);
}

How to filter the data in realm adapter?

public void setFilter( List<RealmPhoneCallLogs> filtedData) {
filter = new ArrayList<>(); // <-- WRONG
filter.addAll(filtedData); // <-- WRONG

private List<RealmPhoneCallLogs> filter(List<RealmPhoneCallLogs> models, String query) {
query = query.toLowerCase();

final List<RealmPhoneCallLogs> filteredModelList = new ArrayList<>(); // <-- WRONG
for (RealmPhoneCallLogs model : models) { // <-- WRONG
final String text = model.getNumber().toLowerCase();
if (text.contains(query)) { // <-- WRONG: should be Realm query

Instead, you need to do

// from https://stackoverflow.com/a/33818311/2413303
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
adapter.getFilter().filter(query);
return false;
}

@Override
public boolean onQueryTextChange(String newText) {
if (searchView.getQuery().length() == 0) {
adapter.getFilter().filter("");
}
return false;
}
});

Then just like in the linked answer

private class MyNamesAdapter
extends RealmBaseAdapter<RealmPhoneCallLogs>
implements Filterable {
public MyNamesAdapter(OrderedRealmCollection<RealmPhoneCallLogs> data) {
super(data);
}

public class ViewHolder {
TextView number;
TextView callType;
TextView startTime;
TextView contactName;
TextView callDuration;
TextView fileSize;
ImageView contactPhoto;
ImageView callSymbol;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
final RealmPhoneCallLogs realmPhoneCallLogs = getItem(position);
ViewHolder viewHolder;
if(convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.calllog_layout, parent, false);
viewHolder.number = (TextView) convertView.findViewById(R.id.missedNumber);
viewHolder.startTime = (TextView) convertView.findViewById(R.id.missedStartTime);
viewHolder.contactName = (TextView) convertView.findViewById(R.id.missedContactName);
viewHolder.callDuration = (TextView) convertView.findViewById(R.id.missedCallDuration);
viewHolder.fileSize = (TextView) convertView.findViewById(R.id.missedFileSize);
viewHolder.contactPhoto = (ImageView) convertView.findViewById(R.id.missedImage);
viewHolder.callSymbol = (ImageView) convertView.findViewById(R.id.callSymbol);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}

if(adapterData != null) {
viewHolder.number.setText(realmPhoneCallLogs.getNumber());
viewHolder.contactName.setText(getContactName(realmPhoneCallLogs.getNumber()));
viewHolder.callDuration.setText(getTime(realmPhoneCallLogs.getCallDuration()));
viewHolder.startTime.setText(realmPhoneCallLogs.getStartTime());
viewHolder.contactPhoto.setImageBitmap(getContactsImage(realmPhoneCallLogs.getNumber()));
String path = new String(Environment.getExternalStorageDirectory() + "/NewCallLogs/" + realmPhoneCallLogs
.getCallRecords());
File file = new File(path);
String size;

long filesize = file.length();
long fileSizeInKB = filesize / 1024;
long fileSizeInMB = fileSizeInKB / 1024;
if(fileSizeInKB >= 1024) {
size = fileSizeInMB + " Mb";
} else {
size = fileSizeInKB + " Kb";
}
if(!size.isEmpty()) {
viewHolder.fileSize.setText(size);
} else {
viewHolder.fileSize.setText(0);
}

if("I".equals(realmPhoneCallLogs.getCallType())) {
viewHolder.callSymbol.setImageResource(R.mipmap.call_received);
} else if("O".equals(realmPhoneCallLogs.getCallType())) {
viewHolder.callSymbol.setImageResource(R.mipmap.call_made);
} else if("UA".equals(realmPhoneCallLogs.getCallStatus())) {
viewHolder.callSymbol.setImageResource(R.mipmap.call_missed);
}

return convertView;
}
return convertView;
}

// filtering
public void filterResults(String text) {
text = text == null ? null : text.toLowerCase().trim();
if(text == null || "".equals(text)) {
updateData(realm.where(RealmPhoneCallLogs.class).findAllSorted("id");
} else {
updateData(realm.where(RealmPhoneCallLogs.class)
.contains("number", text, Case.INSENSITIVE)
.findAllSorted("id"));
}
}

public Filter getFilter() {
return new MyNamesFilter(this);
}

private class MyNamesFilter
extends Filter {
private final MyNamesAdapter adapter;

private MyNamesFilter(MyNamesAdapter adapter) {
super();
this.adapter = adapter;
}

@Override
protected FilterResults performFiltering(CharSequence constraint) {
return new FilterResults();
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
adapter.filterResults(constraint.toString());
}
}
}

For filtering Realm, you can also check out the official documentation on queries.

Invoking updateData on a RealmRecyclerViewAdapter is not actually updating the data

Don't have a peopleList variable in your RealmRecyclerViewAdapter, use getData().


EDIT: This is what your adapter should look like:

class PeopleAdapter
extends RealmRecyclerViewAdapter<Person, PeopleAdapter.PersonViewHolder> {
private final String TAG = "PeopleAdapter" ;
private final boolean DEBUG = true ;

private PersonView.OnPersonClickListener onPersonClickListener ;

static class PersonViewHolder extends RecyclerView.ViewHolder{
PersonView personView ;
PersonViewHolder(PersonView personView) {
super(personView) ;
this.personView = personView ;
}
} // end of PersonViewHolder class

// Constructor
PeopleAdapter(OrderedRealmCollection<Person> data) {
super(data, true, true) ; // data, autoUpdate, updateOnModification
}

void setOnPersonClickListener(PersonView.OnPersonClickListener onPersonClickListener) {
this.onPersonClickListener = onPersonClickListener ;
}

@Override
public PeopleAdapter.PersonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
PersonView view = (PersonView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_view_person, parent, false) ;
return new PersonViewHolder(view) ;
}

@Override
public void onBindViewHolder(PersonViewHolder holder, int position) {
holder.personView.setOnPersonClickListener(onPersonClickListener) ;
holder.personView.setUser(getData().get(position)) ;
}
}

Recyclerview is so slow with a lot of item

I have made this code that could retrieve from realm 50k items and show it in recyclerview:

final RealmResults<AirportR> airps = realm.where(AirportR.class).findAll();
airrports = realm.copyFromRealm(airps, 0);

That's because you're copying 50000 objects from your zero-copy database on your UI thread.

Solutions:

1.) don't copy the elements out from the zero-copy database

2.) copy out 50000 elements into memory on a background thread and have fun with the memory usage


Honestly, it's quite obvious that #2 isn't a real solution, so instead, you should follow the practices of how to use Realm's lazy query evaluation feature and managed objects, rather than trying to hack it by calling realm.copyFromRealm()

So this is solution #1 :

public class ListAirportFragment
extends Fragment {
Realm realm;
RealmResults<AirportR> airps;
RecyclerView recyclerView;

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

RealmConfiguration defaultConfig = new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build();
realm = Realm.getInstance(defaultConfig);

recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
SearchView searchView = (SearchView) rootView.findViewById(R.id.searchview);

airps = realm.where(AirportR.class).findAll();

recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
final AirportAdapter adapter = new AirportAdapter(realm, airps, getActivity());
recyclerView.setAdapter(adapter);

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}

@Override
public boolean onQueryTextChange(String newText) {
adapter.getFilter().filter(newText);
return true;
}
});

return rootView;
}

private class AirportAdapter
extends RecyclerView.Adapter<RecyclerView.ViewHolder>
implements Filterable {
private RealmResults<AirPort> listAirports;
private Context context;
private Realm realm;

private final RealmChangeListener realmChangeListener = new RealmChangeListener() {
@Override
public void onChange(Object element) {
notifyDataSetChanged();
}
};

public AirportAdapter(Realm realm, RealmResults<AirportR> airports, Context context) {
this.realm = realm;
this.listAirports = airports;
this.listAirports.addChangeListener(realmChangeListener);
this.context = context;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.airport_show, parent, false);
AirportClass holder = new AirportClass(view);
return holder;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
AirportR airportR = listAirports.get(position);

AirportClass mHolder = (AirportClass) holder;

mHolder.country.setText(airportR.getIsoCountry());
mHolder.name.setText(airportR.getName());
}

@Override
public int getItemCount() {
if(listAirports == null || !listAirports.isValid()) {
return 0;
}
return listAirports.size();
}

@Override
public Filter getFilter() {
if(filter == null) {
filter = new AirportFilter(this);
}
return filter;
}

private class AirportFilter
extends Filter {
private final AirportAdapter adapter;

private AirportFilter(AirportAdapter adapter) {
super();
this.adapter = adapter;
}

@Override
protected FilterResults performFiltering(CharSequence constraint) {
final FilterResults results = new FilterResults();
return results;
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if(adapter.listAirports != null && adapter.listAirports.isValid()) {
adapter.listAirports.removeChangeListener(adapter.realmChangeListener);
}
if(constraint.length() == 0) {
adapter.listAirports = adapter.realm.where(AirportR.class).findAll();
} else {
final String filterPattern = constraint.toString().toLowerCase().trim();
adapter.listAirports = adapter.realm.where(AirportR.class)
.contains("fieldToQueryBy", filterPattern, Case.INSENSIIVE) // TODO: change field
.findAll();
}
adapter.listAirports.addChangeListener(adapter.realmChangeListener);
adapter.notifyDataSetChanged();
}
}

private class AirportClass
extends RecyclerView.ViewHolder {
TextView name, country;
ImageView image;

public AirportClass(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
country = (TextView) itemView.findViewById(R.id.country);
image = (ImageView) itemView.findViewById(R.id.imageView);
}
}
}
}

This solution didn't use RealmRecyclerViewAdapter and managed adding/removing change listener manually, but you can also use RealmRecyclerViewAdapter for a better looking solution.

Search View in Realm Recycler View Android

Your code looks good just one mistake though

You are filtering result and storing too like below

    filteredModelList = new ArrayList<>();

for (MyColleagueModel model : models) {

final String text = model.getName().toLowerCase().toString();

if (text.contains(query)) {

filteredModelList.add(model);

}

}


Related Topics



Leave a reply



Submit