1

I have two classes, PersonAdapter and AnimalAdapter.

Both classes are almost identical so I'm trying to reduce as much boiler plate code as possible.

PersonAdapter extends from RecyclerView.Adapter.

AnimalAdapter extends from RecyclerView.Adapter. Both classes implements the Filterable interface.

PersonAdapter defines an inner class PersonFilter extending from Filter.

AnimalAdapter defines an inner class AnimalFilter extending from Filter.

I would like to replace PersonFilter and AnimalFilter with some kind of generic class, but I don't know how since these are the differences between both classes:

PersonFilter 
    - works with a List<Person>
    - uses Person#getName() to make a string comparison

AnimalFilter 
    - works with a List<Animal>
    - uses Animal#getType() to make a string comparison

Please take a quick look at both classes:

PersonAdapter

public class PersonAdapter extends RecyclerView.Adapter<PersonViewHolder> implements Filterable {
    private List<Person> persons;
    private PersonFilter personFilter;

    public PersonAdapter(List<Person> persons) {
        this.persons = persons;
    }

    ...

    @Override
    public Filter getFilter() {
        if(personFilter == null)
            personFilter = new PersonFilter(this, persons);
        return personFilter;
    }


    public static class PersonFilter extends Filter {
        private final PersonAdapter adapter;

        private final List<Person> originalList;

        private final List<Person> filteredList;

        private PersonFilter(PersonAdapter adapter, List<Person> originalList) {
            super();
            this.adapter = adapter;
            this.originalList = new LinkedList<>(originalList);
            this.filteredList = new ArrayList<>();
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            filteredList.clear();
            final FilterResults results = new FilterResults();

            if (constraint.length() == 0) {
                filteredList.addAll(originalList);
            } else {
                final String filterPattern = constraint.toString().toLowerCase().trim();

                for (final Person person : originalList) {
                    if (person.getName().toLowerCase().contains(filterPattern.toLowerCase())) {
                        filteredList.add(person);
                    }
                }
            }
            results.values = filteredList;
            results.count = filteredList.size();
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            adapter.persons.clear();
            adapter.persons.addAll((ArrayList<Person>) results.values);
            adapter.notifyDataSetChanged();
        }
    }
}

AnimalAdapter

public class AnimalAdapter extends RecyclerView.Adapter<AnimalViewHolder> implements Filterable {
    private List<Animal> animals;
    private AnimalFilter animalFilter;

    public AnimalAdapter(List<Animal> animals) {
        this.animals = animals;
    }

    ...


    public static class AnimalFilter extends Filter {
        private final AnimalAdapter adapter;

        private final List<Animal> originalList;

        private final List<Animal> filteredList;

        private AnimalFilter(AnimalAdapter adapter, List<Animal> originalList) {
            super();
            this.adapter = adapter;
            this.originalList = new LinkedList<>(originalList);
            this.filteredList = new ArrayList<>();
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            filteredList.clear();
            final FilterResults results = new FilterResults();

            if (constraint.length() == 0) {
                filteredList.addAll(originalList);
            } else {
                final String filterPattern = constraint.toString().toLowerCase().trim();

                for (final Animal animal : originalList) {
                    if (animal.getType().toLowerCase().contains(filterPattern.toLowerCase())) {
                        filteredList.add(animal);
                    }
                }
            }
            results.values = filteredList;
            results.count = filteredList.size();
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            adapter.animals.clear();
            adapter.animals.addAll((ArrayList<Animal>) results.values);
            adapter.notifyDataSetChanged();
        }
    }
}

How should I create a generic subclass of Filter that works both with Animal and Person objects and can be used by PersonAdapter and AnimalAdapter?

Boel
  • 917
  • 2
  • 11
  • 23
  • 1
    Have you considered using generics (for the list difference) and a comparator (for the name/type part)? – Chris K Oct 09 '16 at 15:25
  • 1
    Maybe introduce an abstract class which both `Person` and `Animal` extend, eg: `AbstractMammalAdapter`, since they share some basics. You can still then implement generic Filters like `MammalFilter` – Mad Matts Oct 09 '16 at 15:28
  • @ChrisK, thanks I'll read about the comparator suggestion – Boel Oct 09 '16 at 15:28
  • 1
    @Boel for an example of what I mean by using a comparator (or a predicate); the following SO question has an example of using a filter method on the Google Collections lib: http://stackoverflow.com/questions/122105/what-is-the-best-way-to-filter-a-java-collection – Chris K Oct 09 '16 at 15:30
  • I am cautious of why the Filter class is modifying state in the adapter. This relationship reduces reuse of the Filter and means that you have an extra hurdle to jump over. That said one could use generics to solve your immediately stated problem. I'll knock something together for you and post it. – Chris K Oct 09 '16 at 15:36

1 Answers1

2

Here is a filter that uses generics and a predicate to pull out the two filters from the question. The code could be reduced further by using a method similar to Guava Iterable.filter().

MyAdapterInterface will need to be an interface or shared parent class for the adapters that shares all of the adapter methods that this filter needs to call.

    public class MyFilter<A extends MyAdapterInterface,T> extends Filter {
        private final A adapter;
        private final Predicate<T> comparator; // Java 8 class - can easily be created by hand if you are using pre Java 8
        // see https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html

        private final List<T> originalList;
        private final List<T> filteredList;

        private MyFilter(A adapter, List<T> originalList, Predicate<T> predicate) {
            super();
            this.adapter = adapter;
            this.comparator = comparator;
            this.originalList = new LinkedList<>(originalList);
            this.filteredList = new ArrayList<>();
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            filteredList.clear();
            final FilterResults results = new FilterResults();

            if (constraint.length() == 0) {
                filteredList.addAll(originalList);
            } else {
                final String filterPattern = constraint.toString().toLowerCase().trim();

                for (final T animal : originalList) {
                    if (predicate.test(animal)) {
                        filteredList.add(animal);
                    }
                }
            }
            results.values = filteredList;
            results.count = filteredList.size();
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            adapter.clearResults();   // method to be added to MyAdapterInterface
            adapter.addResults(results.values);  // method to be added to MyAdapterInterface
            adapter.notifyDataSetChanged();
        }
    }
Chris K
  • 11,622
  • 1
  • 36
  • 49
  • 1
    @Boel you are welcome. I may have missed a a place or two where the types will need to be smoothed over, as I do not have the complete code base referenced in the question I have just done my best by eye. At least this will give you a good starting point for one way of how this could be done. – Chris K Oct 09 '16 at 15:56
  • 1
    @Boal also as I suggested previously, but I wanted to keep out of the answer due to scope creep ;) You may have your hands tied by other constraints, however if you can avoid having the filter modify state in the adapter then you will have a cleaner solution. – Chris K Oct 09 '16 at 15:58