Search code examples
androidfilterandroid-recyclerviewandroid-viewmodelandroid-diffutils

Android How to apply multiple filters with androidx.recyclerview.widget.DiffUtil


I am trying to create implement multiple filters for searching data in Recyclerview.

For instance, in a shopping list, I can put filter like computers falling under the following criteria.

Brand = apple , ScreenSize = 10 to 13 inch , HardDisk Size = 250 to 500

Now my plane ViewModel will be having list of computers with all these criteria inside model class ResponseBase, which I am getting from backend.

class ItemViewModel : ViewModel() {

var mResponse : MutableLiveData<ResponseBase>? = null

fun getData() : MutableLiveData<ResponseBase> {
    if(null == mResponse) {
       mResponse = NetworkProcessor().loadSearchData()
    }
    return mResponse as MutableLiveData<ResponseBase>
  }
}

ResponseBase.kt

data class ResponseBase(
    val matches: List<ComputersData>
)

ComputersData.kt

data class ComputersData(
    val brand: String,
    val screenSize: Int,
    val hardDisk: Int,
    val processor: String,
    val display_name: String,
    val ram: Int,
)

Now as per Android Sunflower sample, RecyclerView can have DiffUtil , which can be used for efficient filtering.

But how to filter ComputersData based on criteria like this with DiffUtil inside RecyclerView Adapter?

brand = apple && screenSize = 10 to 13 , hardDisk = 250 to 500

Any idea would be highly appreciated!


Solution

  • MainActivity

    public class MainActivity extends AppCompatActivity {
    
    private RecyclerView mRecyclerView;
    private EmployeeRecyclerViewAdapter mRecyclerViewAdapter;
    private List<Employee> employeeList;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        employeeList = DummyEmployeeDataUtils.getEmployeeList();
        mRecyclerViewAdapter = new EmployeeRecyclerViewAdapter(
                employeeList);
        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mRecyclerViewAdapter);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.sort_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.age_above_employees:        
        mRecyclerViewAdapter.updateEmployeeListItems(DummyEmployeeDataUtils.getEmployessWithAgeAbove50());
                return true;
        }
        return super.onOptionsItemSelected(item);
       }
     }
    

    Create dummy employees list with age

    public class DummyEmployeeDataUtils {
    
    public static List<Employee> getEmployessWithAgeAbove50() {
        final List<Employee> employeeList = getEmployeeList();
        final List<Employee> filteredEmployees = new ArrayList<>();
        for (int i = 0; i < employeeList.size(); i++) {
            if (employeeList.get(i).getAge() > 50)
                filteredEmployees.add(employeeList.get(i));
        }
        return filteredEmployees;
    }
    
    public static List<Employee> getEmployeeList() {
        final List<Employee> employees = new ArrayList<>();
    
        employees.add(new Employee(1, "Employee 1", "Developer", 12));
        employees.add(new Employee(2, "Employee 2", "Tester", 52));
        employees.add(new Employee(3, "Employee 3", "Support", 72));
        employees.add(new Employee(4, "Employee 4", "Sales Manager", 11));
        employees.add(new Employee(5, "Employee 5", "Manager", 64));
        employees.add(new Employee(6, "Employee 6", "Team lead", 99));
        employees.add(new Employee(7, "Employee 7", "Scrum Master", 89));
        employees.add(new Employee(8, "Employee 8", "Sr. Tester", 23));
        employees.add(new Employee(9, "Employee 9", "Sr. Developer", 21));
        return employees;
      }
    }
    

    Here goes our Adapter

    public class EmployeeRecyclerViewAdapter extends
        RecyclerView.Adapter<EmployeeRecyclerViewAdapter
                .ViewHolder> {
    
    private List<Employee> mEmployees = new ArrayList<>();
    
    public EmployeeRecyclerViewAdapter(List<Employee> employeeList) {
        this.mEmployees.addAll(employeeList);
    }
    
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        final View view = inflater.inflate(R.layout.list_item, parent, false);
        return new ViewHolder(view);
    }
    
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final Employee employee = mEmployees.get(position);
        holder.bindView(employee);
    }
    
    public void updateEmployeeListItems(List<Employee> employees) {
        final EmployeeDiffCallback diffCallback = new EmployeeDiffCallback(this.mEmployees, employees);
        final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
        this.mEmployees.clear();
        this.mEmployees.addAll(employees);
        diffResult.dispatchUpdatesTo(this);
    }
    
    @Override
    public int getItemCount() {
        return mEmployees.size();
    }
    
    public static class ViewHolder extends RecyclerView.ViewHolder {
    
        private final TextView role;
        private final TextView name;
        private final TextView age;
    
        public ViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(R.id.employee_name);
            role = (TextView) itemView.findViewById(R.id.employee_role);
            age = (TextView) itemView.findViewById(R.id.employee_age);
        }
    
        void bindView(Employee employee) {
            name.setText(employee.getName());
            role.setText(employee.getRole());
            age.setText("Age ".concat(employee.getAge()+""));
        }
      }
    }
    

    This is the DiffUtil Adapter

    public class EmployeeDiffCallback extends DiffUtil.Callback {
    
    private final List<Employee> mOldEmployeeList;
    private final List<Employee> mNewEmployeeList;
    
    public EmployeeDiffCallback(List<Employee> oldEmployeeList, List<Employee> newEmployeeList) {
        this.mOldEmployeeList = oldEmployeeList;
        this.mNewEmployeeList = newEmployeeList;
    }
    
    @Override
    public int getOldListSize() {
        return mOldEmployeeList.size();
    }
    
    @Override
    public int getNewListSize() {
        return mNewEmployeeList.size();
    }
    
    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldEmployeeList.get(oldItemPosition).getId() == mNewEmployeeList.get(
                newItemPosition).getId();
    }
    
    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        final Employee oldEmployee = mOldEmployeeList.get(oldItemPosition);
        final Employee newEmployee = mNewEmployeeList.get(newItemPosition);
    
        return oldEmployee.getName().equals(newEmployee.getName());
    }
    
    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        // Implement method if you're going to use ItemAnimator
        return super.getChangePayload(oldItemPosition, newItemPosition);
       }
    }
    

    UPDATED ANSWER

    EDIT TO ANSWER from Manoj. Updated by Author for future reference

    The above answer works fine in case of RecyclerView without ViewModels. Once viewmodel comes to picture and RecyclerView listens to changes in VM, the logic should be to update the filtered list with new results in ViewModel itself. So that recycler view listens to the updates all time and keep data updated.

    In this scenario, Diff Utils may not come in handly. Rather efficient way (As the Question is in Kotlin) is with Filters

    I found filters much more simpler than Diff Util implementation.

    For future reference , Sample filter approach with multiple predicates are as follows

    mViewModelObject is the instance of View Model object

    val valueOne =  mViewModelObject.criteriaOne.value
    val valueTwo =  mViewModelObject.criteriaTwo.value
    
     val newList  = originalList.filter {
                        it.listVariableABC == valueOne 
                        &&
                        it.listVariablePQR == valueTwo
    }
    

    Once the new list is created out of multiple criteria, update the same in ViewModel. So that Recycler View will get updated list.