The recycler view is a popular way to create dynamic lists and has largely replaced the old-fashioned list view. However, like any tool, it has its advantages and disadvantages. The recycler view recycles views that are not visible on the screen, which can cause issues when handling dynamic content. Let's consider an example of a recycler view containing a list of edit text.
class EditTextAdapter(private val items: List<String>) :
RecyclerView.Adapter<EditTextViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = EditTextViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_edit_text, parent, false)
)
override fun onBindViewHolder(holder: EditTextViewHolder, position: Int) {
}
override fun getItemCount() = items.size
}
class EditTextViewHolder(view: View) : RecyclerView.ViewHolder(view)
The adapter shown above passes a list of empty items and inflates the recycler view using our EditTextViewHolder
. The resulting output of this adapter will resemble the following:
Once the user input views go off-screen, they get recycled and the recycler view is unable to distinguish between the empty view and input view due to the dynamic data entered by the user. As a result, when the user scrolls down the list, the input values are repeatedly shown.
Solution 1
One solution is to make the recycler view non-recyclable by using <ViewHolder>.setIsRecyclable(false)
. This will prevent the views from replacing each other, and the user input values will persist.
override fun onBindViewHolder(holder: EditTextViewHolder, position: Int) {
holder.setIsRecyclable(false)
}
Although this approach is suitable for small lists, it is not ideal for larger or paged lists as it defeats the purpose of the recycler view. Instead, I highly recommend the second solution as it is scalable and works in all types of situations.
Solution 2
class EditTextAdapter(private val items: List<String>) :
RecyclerView.Adapter<EditTextViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = EditTextViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_edit_text, parent, false)
)
override fun onBindViewHolder(holder: EditTextViewHolder, position: Int) {
}
override fun getItemCount() = items.size
override fun getItemViewType(position: Int) = position
override fun getItemId(position: Int) = position.toLong()
}
class EditTextViewHolder(view: View) : RecyclerView.ViewHolder(view)
As you can see, I've overridden two new methods - getItemViewType
and getItemId
. The former provides a unique viewType
to each item in a recycler view (default value is 0), while the latter provides a unique ID (default is RecyclerView.NO_ID, which is -1).
These methods inform the recycler view that a particular view is unique and is to be recycled while preserving dynamic values. In our case, we are passing the position to both methods since it is guaranteed to be unique for every item. If you run your code now, you'll see that the issue has been resolved.
Here are a couple of points to keep in mind:
- If you are already using
getItemViewType
(e.g., to display different layouts like a chat list), ensure that you assign a unique value to every element. For instance, use even and odd multiples of the position. This way, you can differentiate between your view types while still assigning a different viewType to every item. - This method is not limited to just edit text - it will work with all dynamic inputs like checkboxes, radio buttons, sliders, spinners, etc.
And that's it! You can now use edit text or any other dynamic item in your recycler view without any worries.
Cheers! 🍻