最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

android - RecyclerView Adapter onCreateViewHolder ReInvocation Issue Despite Single Item in List - Stack Overflow

programmeradmin1浏览0评论

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's areItemsTheSame method always returns true (indicating that items are the same), and areContentsTheSame returns false (indicating that the content has changed).

  • Despite these conditions, onCreateViewHolder is being called repeatedly, not just onBindViewHolder.

    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 the onCreateViewHolder 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's areItemsTheSame method always returns true (indicating that items are the same), and areContentsTheSame returns false (indicating that the content has changed).

  • Despite these conditions, onCreateViewHolder is being called repeatedly, not just onBindViewHolder.

    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 the onCreateViewHolder 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
Add a comment  | 

1 Answer 1

Reset to default 0

You'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.

发布评论

评论列表(0)

  1. 暂无评论