Problem Statement :
I'm facing an issue where the onCreateViewHolder
method in my RecyclerView.Adapter
is being called multiple times, even though there is only one item in the list. This is unexpected because, according to the logic, only the onBindViewHolder
method should be invoked when updating the data.
Key Observations:
The
RecyclerView
has only one item in the list.The
DiffUtil
callback'sareItemsTheSame
method always returnstrue
(indicating that items are the same), andareContentsTheSame
returnsfalse
(indicating that the content has changed).Despite these conditions,
onCreateViewHolder
is being called repeatedly, not justonBindViewHolder
.class MainActivity : AppCompatActivity() { private var uiState = Item("Banner 2", "Initial Call") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val recyclerView: RecyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) val adapter = MyAdapter() recyclerView.adapter = adapter Log.d("[HelloTesting]", "data hashcode is before copy is ${uiState.hashCode()}") adapter.update(uiState) lifecycleScope.launchWhenCreated { delay(5000) uiState = uiState.copy("Banner 2", "Banner 2 ${Random.nextInt()}") Log.d("[HelloTesting]", "data hashcode is after copy is ${uiState.hashCode()}") adapter.update(uiState) } } }
Code Behavior:
The issue occurs when I update the data using
adapter.update(uiState)
inside a coroutine.After updating, the entire
ViewHolder
seems to be recreated, causing theonCreateViewHolder
method to be called again unexpectedly, despite having only one item.
// MyAdapter
internal class MyAdapter : RecyclerView.Adapter<SpotlightViewHolder>() {
private val items = mutableListOf(Item("Banner1 ", "Banner 1 ${Random.nextInt()}"))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpotlightViewHolder {
val holder = SpotlightViewHolder(ComposeView(parent.context))
Log.d("[HelloTesting]", "onCreate ${holder.hashCode()}")
return holder
}
override fun onBindViewHolder(holder: SpotlightViewHolder, position: Int) {
Log.d("[HelloTesting]", "onBindViewHolder ${holder.hashCode()}")
holderpose.apply {
setContent {
val item = items[position]
Log.d("[HelloTesting]", "item is $item")
Text(text = item.title, fontSize = 60.sp, color = Color.Red)
}
}
}
@SuppressLint("NotifyDataSetChanged")
fun update(data: Item) {
val list = listOf(data)
applyDataSetChanged(list)
}
private fun applyDataSetChanged(newData: List<Item>) {
val diffCallback = PayComponentDiffUtil(items, newData)
val diffResult = DiffUtil.calculateDiff(diffCallback)
items.clear()
items.addAll(newData)
diffResult.dispatchUpdatesTo(this)
}
override fun getItemCount() = items.size
}
Specific Question:
What is causing the onCreateViewHolder
to be invoked multiple times, and why is this happening even though the RecyclerView
only has a single item and the DiffUtil
callback should indicate minimal changes?
I would appreciate any insights into why this behavior is occurring, and how I can prevent the ViewHolder
from being recreated unnecessarily.
// DiffUtil Callback
internal class PayComponentDiffUtil(
private val oldList: List<Item>, private val newList: List<Item>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return true // Assuming items are the same based on unique identifier
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return false // Assuming the content has changed
}
}
Problem Statement :
I'm facing an issue where the onCreateViewHolder
method in my RecyclerView.Adapter
is being called multiple times, even though there is only one item in the list. This is unexpected because, according to the logic, only the onBindViewHolder
method should be invoked when updating the data.
Key Observations:
The
RecyclerView
has only one item in the list.The
DiffUtil
callback'sareItemsTheSame
method always returnstrue
(indicating that items are the same), andareContentsTheSame
returnsfalse
(indicating that the content has changed).Despite these conditions,
onCreateViewHolder
is being called repeatedly, not justonBindViewHolder
.class MainActivity : AppCompatActivity() { private var uiState = Item("Banner 2", "Initial Call") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val recyclerView: RecyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) val adapter = MyAdapter() recyclerView.adapter = adapter Log.d("[HelloTesting]", "data hashcode is before copy is ${uiState.hashCode()}") adapter.update(uiState) lifecycleScope.launchWhenCreated { delay(5000) uiState = uiState.copy("Banner 2", "Banner 2 ${Random.nextInt()}") Log.d("[HelloTesting]", "data hashcode is after copy is ${uiState.hashCode()}") adapter.update(uiState) } } }
Code Behavior:
The issue occurs when I update the data using
adapter.update(uiState)
inside a coroutine.After updating, the entire
ViewHolder
seems to be recreated, causing theonCreateViewHolder
method to be called again unexpectedly, despite having only one item.
// MyAdapter
internal class MyAdapter : RecyclerView.Adapter<SpotlightViewHolder>() {
private val items = mutableListOf(Item("Banner1 ", "Banner 1 ${Random.nextInt()}"))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpotlightViewHolder {
val holder = SpotlightViewHolder(ComposeView(parent.context))
Log.d("[HelloTesting]", "onCreate ${holder.hashCode()}")
return holder
}
override fun onBindViewHolder(holder: SpotlightViewHolder, position: Int) {
Log.d("[HelloTesting]", "onBindViewHolder ${holder.hashCode()}")
holder.compose.apply {
setContent {
val item = items[position]
Log.d("[HelloTesting]", "item is $item")
Text(text = item.title, fontSize = 60.sp, color = Color.Red)
}
}
}
@SuppressLint("NotifyDataSetChanged")
fun update(data: Item) {
val list = listOf(data)
applyDataSetChanged(list)
}
private fun applyDataSetChanged(newData: List<Item>) {
val diffCallback = PayComponentDiffUtil(items, newData)
val diffResult = DiffUtil.calculateDiff(diffCallback)
items.clear()
items.addAll(newData)
diffResult.dispatchUpdatesTo(this)
}
override fun getItemCount() = items.size
}
Specific Question:
What is causing the onCreateViewHolder
to be invoked multiple times, and why is this happening even though the RecyclerView
only has a single item and the DiffUtil
callback should indicate minimal changes?
I would appreciate any insights into why this behavior is occurring, and how I can prevent the ViewHolder
from being recreated unnecessarily.
// DiffUtil Callback
internal class PayComponentDiffUtil(
private val oldList: List<Item>, private val newList: List<Item>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return true // Assuming items are the same based on unique identifier
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return false // Assuming the content has changed
}
}
Share
Improve this question
asked Feb 5 at 16:16
Akshat SinghalAkshat Singhal
214 bronze badges
1 Answer
Reset to default 0You're missing a key component here - partial update payload.
Without it RecyclerView does not know what is changed so it does the most sensible thing - creates a new ViewHolder for given position.
If you want to execute a partial update you have to override DiffUtil.GetChangePayload
to generate payloads. It can be something trivial like a boolean flag.
Then your Adapter must override 3-argument overload of Adapter.OnBindViewHolder(VH Holder, int position, List<Object> payloads)
to consume those payloads to update ViewHolder in-place.