https://kotlinlang.org logo
#compose
Title
# compose
g

Gerardo Ernesto Rodriguez Navar

01/21/2022, 11:48 AM
Hi everyone! I have a problem. I have a
ViewHolder
that uses compose to render, but when I click it, the whole screen jumps. Here is the video:
It’s a AndroidView composable, which contains a recycler view, it’s adapter and the viewHolder mentioned
I thought maybe it had to do with something related to the recyclerView or the AndroidView, but I made a new project with a recyclerView using xml and the problem is the same. Something related to the rendering of the ViewHolder
Here is the code:
COMPOSABLE THAT CONTAINS THE RECYCLERVIEW @Composable private fun DateSelector( dates: List<BookingSelectTimeViewModel.DateChip>, onDateSelected: (dateChip: BookingSelectTimeViewModel.DateChip) -> Unit ) { val dateSelectorAdapter = remember { DateSelectorAdapter() } AndroidView( factory = { context -> val recyclerView = RecyclerView(context) val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) with(recyclerView) { layoutManager = linearLayoutManager adapter = dateSelectorAdapter overScrollMode = View.OVER_SCROLL_NEVER } dateSelectorAdapter.setOnDateSelectedListener { dateChip -> onDateSelected(dateChip) } recyclerView }, update = { dateSelectorAdapter.setData(dates) } ) }
ADAPTER private class DateSelectorAdapter : RecyclerView.Adapter<DateSelectorAdapter.DateChipViewHolder>() { private val dates = mutableListOf<BookingSelectTimeViewModel.DateChip>() private var onDateSelectedListener: ((dateChip: BookingSelectTimeViewModel.DateChip) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DateChipViewHolder { return DateChipViewHolder(ComposeView(parent.context)) } override fun onViewRecycled(holder: DateChipViewHolder) { holder.composeView.disposeComposition() super.onViewRecycled(holder) } override fun onBindViewHolder(holder: DateChipViewHolder, position: Int) { holder.bind(dates[position]) } override fun getItemCount(): Int { return dates.size } fun setData(dates: List<BookingSelectTimeViewModel.DateChip>) { this.dates.clear() this.dates.addAll(dates) notifyDataSetChanged() } fun setOnDateSelectedListener(onDateSelectedListener: (dateChip: BookingSelectTimeViewModel.DateChip) -> Unit) { this.onDateSelectedListener = onDateSelectedListener } inner class DateChipViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) { init { composeView.setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) } fun bind(dateChip: BookingSelectTimeViewModel.DateChip) { composeView.setContent { DateChip( onClick = { onDateSelectedListener?.invoke(dateChip) }, isSelected = dateChip.isSelected, text = dateChip.formattedDate, modifier = Modifier .padding( vertical = 8.dp, horizontal = 4.dp ) .defaultMinSize(minHeight = 48.dp) ) } } } }
COMPOSABLE IN THE VIEWHOLDER @Composable private fun DateChip( onClick: () -> Unit, isSelected: Boolean, text: String, modifier: Modifier = Modifier ) { Button( onClick = onClick, shape = RoundedCornerShape(percent = 50), colors = ButtonDefaults.buttonColors( backgroundColor = if (isSelected) MaterialTheme.colors.primary else MaterialTheme.colors.background ), elevation = ButtonDefaults.elevation( defaultElevation = 0.dp, pressedElevation = 0.dp ), modifier = modifier ) { Text( text = text, color = if (isSelected) MaterialTheme.colors.onPrimary else MaterialTheme.colors.primary ) } }
DATE CHIP MODEL data class DateChip( val formattedDate: String, val isSelected: Boolean )
If anyone has an idea why this could be happening please let me know 🙏 🙏 😄
d

Dominaezzz

01/21/2022, 12:24 PM
I had to watch that video way too many times. 😄
I think when you dispose the composition, it makes the UI jitter. The view is removed then reinserted.
To test my theory, I want you to make some changes. Remove the overriden onViewRecycled method Create a mutable state of DateChip in your viewholder. Move the contents of bind to the constructor, using the mutable state as the dateChip parameter. Then set the mutable state in your bind method. Does it work better now?
g

Gerardo Ernesto Rodriguez Navar

01/21/2022, 1:36 PM
Thanks @Dominaezzz. I just tried as you said (remove onViewRecycler + modifications to ViewHolder) but the problem persists. Here is the code of the modified adapter:
Copy code
NEW ADAPTER AND VIEWHOLDER

private class DateSelectorAdapter : RecyclerView.Adapter<DateSelectorAdapter.DateChipViewHolder>() {
    private val dates = mutableListOf<DateChip>()
    private var onDateSelectedListener: ((dateChip: DateChip) -> Unit)? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DateChipViewHolder {
        return DateChipViewHolder(ComposeView(parent.context))
    }

    /*override fun onViewRecycled(holder: DateChipViewHolder) {
        holder.composeView.disposeComposition()
        super.onViewRecycled(holder)
    }*/

    override fun onBindViewHolder(holder: DateChipViewHolder, position: Int) {
        holder.bind(dates[position])
    }

    override fun getItemCount(): Int {
        return dates.size
    }

    fun setData(dates: List<DateChip>) {
        this.dates.clear()
        this.dates.addAll(dates)
        notifyDataSetChanged()
    }

    fun setOnDateSelectedListener(onDateSelectedListener: (dateChip: DateChip) -> Unit) {
        this.onDateSelectedListener = onDateSelectedListener
    }

    inner class DateChipViewHolder(val composeView: ComposeView) :
        RecyclerView.ViewHolder(composeView) {

        private var dateChip by mutableStateOf<DateChip?>(null)

        init {
            composeView.setViewCompositionStrategy(
                ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
            )

            composeView.setContent {
                DateChip(
                    onClick = {
                        dateChip?.let {
                            onDateSelectedListener?.invoke(it)
                        }
                    },
                    isSelected = dateChip?.isSelected ?: false,
                    text = dateChip?.formattedDate ?: "",
                    modifier = Modifier
                        .padding(
                            vertical = 8.dp,
                            horizontal = 4.dp
                        )
                        .defaultMinSize(minHeight = 48.dp)
                )
            }
        }

        fun bind(dateChip: DateChip) {
            this.dateChip = dateChip
        }
    }
}
I slowed down the video and took this screenshot. Might be useful:
d

Dominaezzz

01/21/2022, 2:02 PM
Mind sharing the slowed down video?
g

Gerardo Ernesto Rodriguez Navar

01/21/2022, 2:06 PM
Of course. It increased a lot in time jeje, just a warning 😆
On 55 seconds is the first move
d

Dominaezzz

01/21/2022, 2:12 PM
I see, I'm not sure what's going on then.
g

Gerardo Ernesto Rodriguez Navar

01/21/2022, 2:53 PM
No worries. Thanks for the help 😄