Home > Net >  Kotlin recycler view cannot change background when later calling a function
Kotlin recycler view cannot change background when later calling a function

Time:01-14

I'm new to Kotlin and so if you do find rubbish code here and poor practices, do let me know. Otherwise, here is the issue I am having.

I am writing a tiny app that presents users with multiple questions from which they have to select the correct answer. If they select the correct answer, the option is supposed to be highlighted green for 250ms and then they move on to the next question. Otherwise, select the incorrect answer. The logic for moving onto the next question is defined in the main activity, and the background change logic is defined in the adapter class. Below is what the adapter class looks like at the moment (I've only included that which I think is relevant just to add too much faff):


class QuestionOptionAdapter(
    private val items: ArrayList<String>,
    private val correctAnswer: String,
) : RecyclerView.Adapter<QuestionOptionAdapter.ViewHolder>() {

    var onSelectedAnswer: (String) -> Unit = {}
    var onSelectedCorrectAnswer: () -> Unit = {}
    var onSelectedIncorrectAnswer: () -> Unit = {}

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        holder.tvOption.text = item
        holder.tvOption.setOnClickListener {
            if (item == correctAnswer) {
                runBlocking {
                    it.background =
                        ContextCompat.getDrawable(it.context, R.drawable.question_opt_correct)

                    delay(250)
                }
                onSelectedCorrectAnswer()


            } else {
                it.background =
                    ContextCompat.getDrawable(it.context, R.drawable.question_opt_incorrect)

                onSelectedIncorrectAnswer()
            }

           
        }
    }
}

I realised that although the code to changes the background is executed before onSelectedCorrectAnswer(), it won't change the background colour until the entire block has finished executing. Therefore, the user never sees the updated background.

Is there a way to show an update before the block finishes executing?

CodePudding user response:

runBlocking doesn't work because it blocks. It will just wait for the whole time you delay and block the main thread so the device will be frozen and not show any visual changes until it returns.

You need to pass the Activity or Fragment's CoroutineScope into the adapter for the adapter to use. You can then launch a coroutine that won't block the main thread when you delay inside it.

Here I lifted the coroutine to ecompass all your click listener logic. That will make it easier to modify the behavior later if you want.

class QuestionOptionAdapter(
    private val scope: CoroutineScope,
    private val items: ArrayList<String>,
    private val correctAnswer: String,
) : RecyclerView.Adapter<QuestionOptionAdapter.ViewHolder>() {

    var onSelectedAnswer: (String) -> Unit = {}
    var onSelectedCorrectAnswer: () -> Unit = {}
    var onSelectedIncorrectAnswer: () -> Unit = {}

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        holder.tvOption.text = item
        holder.tvOption.setOnClickListener { view ->
            lifecycleScope.launch {
                val isCorrect = item == correctAnswer
                val colorDrawable = 
                    if (isCorrect) R.drawable.question_opt_correct 
                    else R.drawable.question_opt_incorrect
                view.background = ContextCompat.getDrawable(view.context, colorDrawable)
                if (isCorrect) {
                    delay(250)
                    onSelectedCorrectAnswer()
                } else {
                    onSelectedIncorrectAnswer()
                }
            }
        }
    }
}

Actually, you probably want to also prevent the user from clicking other options during that 250ms delay, so you should set a Boolean that can disable further clicking of items during the delay:

class QuestionOptionAdapter(
    private val scope: CoroutineScope,
    private val items: ArrayList<String>,
    private val correctAnswer: String,
) : RecyclerView.Adapter<QuestionOptionAdapter.ViewHolder>() {

    var onSelectedAnswer: (String) -> Unit = {}
    var onSelectedCorrectAnswer: () -> Unit = {}
    var onSelectedIncorrectAnswer: () -> Unit = {}

    private var isLockClickListeners = false

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        holder.tvOption.text = item
        holder.tvOption.setOnClickListener { view ->
            if (isLockClickListeners) {
                return@setOnClickListener
            }
            lifecycleScope.launch {
                val isCorrect = item == correctAnswer
                val colorDrawable = 
                    if (isCorrect) R.drawable.question_opt_correct 
                    else R.drawable.question_opt_incorrect
                view.background = ContextCompat.getDrawable(view.context, colorDrawable)
                if (isCorrect) {
                    isLockClickListeners = true
                    delay(250)
                    onSelectedCorrectAnswer()
                    isLockClickListeners = false
                } else {
                    onSelectedIncorrectAnswer()
                }
            }
        }
    }
}
  • Related