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 filteredList
size 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
How to Write Vertically in a Textview in Android
How to Call a Local Web Service from an Android Mobile Application
Installation Error: Install_Parse_Failed_Manifest_Malformed
Cursor Size Limit in Android SQLitedatabase
How to Turn On/Off Wifi Hotspot Programmatically in Android 8.0 (Oreo)
How to Add Onclick Listener to Recycler View
High Resolution Image - Outofmemoryerror
Permission Denial: This Requires Android.Permission.Interact_Across_Users_Full
Lost My Keystore for Uploaded App on Android Market
Edittext Error Icon and Show Password Missplaced
Receive Gcm Notification Even When the App Is Closed (Slide/Swiped Away)
Android Ci Build: Could Not Find Aapt2-Proto.Jar