Sreehari
Sreehari

Reputation: 5655

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!

Upvotes: 3

Views: 2081

Answers (1)

Manoj Perumarath
Manoj Perumarath

Reputation: 10214

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.

Upvotes: 2

Related Questions