Code optimization is one of the most core features of a good developer. As far as Android developers are concerned the better the optimised code the better is the performance.
Every time creating a new separate class for each adapter for a simple RecyclerView makes the code redundant.
- Create a new adapter class
- Extend with RecyclerView.Adapter
- Create another new class
- Extend with RecyclerView.ViewHolder
- Write logic in onBindView
Except for the point no. 5 all steps are repeatable, that means with every new adapter we need to write codes for all points. So, a Custom Adapter can find a way for this where all points except 5 will be common for all and 5th point will be overridden to write your logic.
- CustomAdapter is a generic adapter abstract class which will optimize code and avoid writing redundant code.
- CustomAdapter extends RecyclerView.Adapter and has its own ViewHolder class extending RecyclerView.ViewHolder name as ItemViewHolder.
- CustomAdapter has a generic type enclosed in anchor bracket <T> which represents what type of data it will hold and show as a list. For example, data can be a list of Car as ArrayList<Car> cars, list of Person as ArrayList<Person>. So <T> here represents <Car>, <Person> etc
- CustomAdapter also takes layout resource id as a constructor on how the items will be mapped from the list of data in UI.
- In this class, bindView is an abstract class to fulfil above point no. 5. To write your business logic.
abstract class CustomAdapter<T>( private val layoutResId : Int, private var mList : ArrayList<T> ) : androidx.recyclerview.widget.RecyclerView.Adapter<ItemViewHolder>() { override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { viewBinder(holder, mList[position], position) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { val view : View = LayoutInflater.from(parent.context) .inflate(layoutResId, parent, false) return ItemViewHolder(view) } override fun getItemCount(): Int = mList.size //=============================== unoverriden methods ================================== fun setList(list : ArrayList<T>) { mList = list notifyDataSetChanged() } fun getList() : ArrayList<T> = this.mList fun addMoreList(list : ArrayList<T>, isClear: Boolean = false) { if (isClear) { mList.clear() } mList.addAll(list) notifyDataSetChanged() } fun clearList() { mList.clear() notifyDataSetChanged() } fun addItem(item : T) { mList.add(item) notifyDataSetChanged() } fun removeLastItem() { if (mList.size > 0) { mList[mList.size - 1] } notifyDataSetChanged() } fun removeItem(item : T) { if (mList.isNotEmpty()) { mList.remove(item) notifyDataSetChanged() } } fun isListEmpty() : Boolean = mList.isEmpty() //================================ abstract methods ===================================== abstract fun viewBinder(holder: ItemViewHolder, objects : T, position: Int) //==================================== private members ======================================== } class ItemViewHolder(view: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(view)
FilterableAdapter is an extension of the CustomAdapter. In other words, giving extra functionality to CustomAdapter.
There are many scenarios where we want to filter the list, in order to achieve this we write full fledged code, performing operations on the list like add, remove clear etc.
The steps goes like :
- Create new adapter class
- Extend with CustomAdapter<T> and Implementing Filterable interface
- Create another new class
- Extend with RecyclerView.ViewHolder
- Write logic in onBindView
- Create filter class extending Filter class
- Perform the Filter operation on the basis of some filtering predicates like person.age > 50, or person.name.startsWith(“A”) etc.
Same as CustomAdapter, FilterableAdapter will focus on point No. 7 the business logic.
- Create an adapter extending FilterableAdapter<T> , T can be any data on which you want to person filtration.
- Override performFilterating method which will provide filtration predicate as its parameter and the return type is ArrayList<T> which is a filter list filtered on the basis of filtration predicate from the original list.
- Add TextWatcher to search editText and on onTextChange write a line filterableAdapter.getFilter().filter(text). Here filterableAdapter is the object of FilterableAdapter, getFilter is the member method, filter is the member method of Filter class and text is what wrote on editText which we get as parameter in onTextChange method.
abstract class FilterableAdapter<T>( private var mList : ArrayList<T>, private val layoutResId : Int ) : CustomAdapter<T>(layoutResId, mList), Filterable { abstract fun performFiltering(filterationText: String): ArrayList<T> override fun getItemCount(): Int = mList.size override fun getFilter(): Filter { if (customFilter != null) { mList.clear() mList.addAll(tempList) } if (customFilter == null) customFilter = CustomFilter() return customFilter as CustomFilter } //====================================== private members ======================================= private var customFilter : CustomFilter? = null private var tempList : ArrayList<T> init { tempList = ArrayList<T>(mList) } private inner class CustomFilter : Filter() { override fun performFiltering(constraint: CharSequence?): FilterResults { val fResult : FilterResults = FilterResults() var filterList : ArrayList<T> = ArrayList() if (constraint != null && constraint.length > 0) { filterList = performFiltering(constraint.toString()) fResult.values = filterList } else { fResult.values = mList } fResult.count = filterList.size return fResult } override fun publishResults(constraint: CharSequence?, results: FilterResults?) { mList.clear() if (!constraint!!.isEmpty() || results!!.count != 0) { mList.addAll(results!!.values as ArrayList<T>) } else { mList.addAll(tempList) } notifyDataSetChanged() } } }
Thanks for giving your valuable time. Keep reading and keep learning