Chapter 10 - Challenge: RecyclerView View Types

I’m having issues with Chapter 10’s challenge regarding adding an extra row to the recyclerview. I am unsure on how to return multiple ViewHolders from onCreateViewHolder() with the way the book has you set up the onCreateViewHolder() returning an instance of CrimeHolder.

I have made an extra xml file, created a new ViewHolder for it called CrimeHolderPolice, and edited the getItemViewCount() to set values for when there is a crime that needs police. With the way that the book has you set up the original onCreateViewHolder though it’s returning an instance of the class CrimeHolder that needs a binding of the original xml file and wont accept a binding of my new xml file.

I have tried to set the return type to ViewHolder but I get an error when I do so. I would like to complete this challenge before moving on to solidify my understanding of RecyclerViews but I’m having difficulty figuring this challenge out and I cannot find any information regarding this challenge using ViewBinding.

Any help would be much appreciated.

class CrimeHolder(
    private val binding: ListItemCrimeBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(crime: Crime){
        binding.crimeTitle.text = crime.title
        binding.crimeDate.text = crime.date.toString()

        binding.root.setOnClickListener{
            Toast.makeText(binding.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT).show()
        }
    }
}

class CrimeHolderPolice(
    private val binding: ListItemCrimePoliceBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(crime: Crime){
        binding.crimeTitle.text = crime.title
        binding.crimeDate.text = crime.date.toString()
        binding.crimePolice.text = crime.needPolice.toString()

        binding.root.setOnClickListener{
            Toast.makeText(binding.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT).show()
        }
    }
}

class CrimeListAdapter(private val crimes: List<Crime>): RecyclerView.Adapter<CrimeHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {

        /*
        if(viewType == 1){
            val inflater = LayoutInflater.from(parent.context)
            val binding = ListItemCrimePoliceBinding.inflate(inflater, parent, false)
            return CrimeHolderPolice(binding)
        }
         */

        val inflater = LayoutInflater.from(parent.context)
        val binding = ListItemCrimeBinding.inflate(inflater, parent, false)
        return CrimeHolder(binding)
    }

    override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
        val crime = crimes[position]
        holder.bind(crime)
    }

    override fun getItemCount() = crimes.size

    override fun getItemViewType(position: Int): Int {
        return when(crimes[position].needPolice){
            true -> 1
            else -> 2
        }
    }
}

Your CrimeListAdapter needs a SINGLE type for its ViewHolder. So you could do something like this:

class CrimeListAdapter(...): RecyclerView.Adapter<ViewHolder>() {...

but then you have to do some type checking in CrimeListAdapter.

Another option is to use a sealed class. This isn’t the entire code snippet, but it might give you something to start working on:

sealed class BaseCrimeHolder(private val rootView: View) : RecyclerView.ViewHolder(rootView) {
    class CrimeHolder(
        private val binding: ListItemCrimeBinding
    ) : BaseCrimeHolder(binding.root) {
        fun bind(crime: Crime) {
            binding.crimeTitle.text = crime.title
            binding.crimeDate.text = crime.date.toString()

            binding.root.setOnClickListener {
                Toast.makeText(
                    binding.root.context,
                    "${crime.title} clicked!",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    class PoliceHolder(
        private val binding: ListItemPoliceBinding
    ) : BaseCrimeHolder(binding.root) {
        fun bind(crime: Crime) {
            // ...
        }
    }
}
1 Like

Thank you for your help, I appreciate it. Setting the return type of CrimeListAdapter to ViewHolder works but then it messed things up with the onBindViewHolder() and its parameters. Ive tried to fix it but have run into an issue instantiating an instance of the CrimeHolder class. I also tried the sealed class and couldn’t get it working but I will keep messing with it.

At this point, I think I might just put this code into a new branch and see if I can figure it out in my spare time and just move onto the next chapter. I think I understand the premise behind what is going on with adding a second row, however Android Studio/Kotlin has so many weird things that it feels like whack-a-mole with errors.

Here’s the code I have right now. I will keep messing with it as I move on in the book.

/*
sealed class Demo(private val rootView: View) : RecyclerView.ViewHolder(rootView){
    class CrimeHolder(private val binding: ListItemCrimeBinding): Demo(binding.root) {
        fun bind(crime: Crime){
            binding.crimeTitle.text = crime.title
            binding.crimeDate.text = crime.date.toString()
            binding.root.setOnClickListener{
                Toast.makeText(binding.root.context,
                    "${crime.title} clicked!",
                    Toast.LENGTH_SHORT).show()
            }
        }
    }
    class CrimeHolderPolice(private val binding: ListItemCrimePoliceBinding) : Demo(binding.root) {
        fun bind(crime: Crime){
            binding.crimeTitle.text = crime.title
            binding.crimeDate.text = crime.date.toString()
            binding.crimePolice.text = crime.needPolice.toString()

            binding.root.setOnClickListener{
                Toast.makeText(binding.root.context,
                    "${crime.title} clicked!",
                    Toast.LENGTH_SHORT).show()
            }
        }
    }
}
 */


class CrimeHolder(private val binding: ListItemCrimeBinding) : RecyclerView.ViewHolder(binding.root) {
    fun bind(crime: Crime){
        binding.crimeTitle.text = crime.title
        binding.crimeDate.text = crime.date.toString()

        binding.root.setOnClickListener{
            Toast.makeText(binding.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT).show()
        }
    }
}


class CrimeHolderPolice(private val binding: ListItemCrimePoliceBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(crime: Crime){
        binding.crimeTitle.text = crime.title
        binding.crimeDate.text = crime.date.toString()
        binding.crimePolice.text = crime.needPolice.toString()

        binding.root.setOnClickListener{
            Toast.makeText(binding.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT).show()
        }
    }
}

class CrimeListAdapter(private val crimes: List<Crime>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

        // Sealed class? Need to look into it.

        if(viewType == 1){
            val inflater = LayoutInflater.from(parent.context)
            val binding = ListItemCrimePoliceBinding.inflate(inflater, parent, false)
            return CrimeHolderPolice(binding)
        }

        val inflater = LayoutInflater.from(parent.context)
        val binding = ListItemCrimeBinding.inflate(inflater, parent, false)
        return CrimeHolder(binding)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

        val hold = CrimeHolder(ListItemCrimeBinding) // Error for some reason
        // Need to make a new instance of CrimeHolder because as it stands now, there is no reference
        // To the CrimeHolder class and no way to access the bind function. Cant change it to anything 
        // else because it is a parameter and set to val.

        val crime = crimes[position]
        hold.bind(crime)
    }

    override fun getItemCount() = crimes.size

    override fun getItemViewType(position: Int): Int {
        return when(crimes[position].needPolice){
            true -> 1
            else -> 2
        }
    }
}

Code for the sealed class

sealed class Demo(private val rootView: View) : RecyclerView.ViewHolder(rootView){
    class CrimeHolder(private val binding: ListItemCrimeBinding): Demo(binding.root) {
        fun bind(crime: Crime){
            binding.crimeTitle.text = crime.title
            binding.crimeDate.text = crime.date.toString()
            binding.root.setOnClickListener{
                Toast.makeText(binding.root.context,
                    "${crime.title} clicked!",
                    Toast.LENGTH_SHORT).show()
            }
        }
    }
    class CrimeHolderPolice(private val binding: ListItemCrimePoliceBinding) : Demo(binding.root) {
        fun bind(crime: Crime){
            binding.crimeTitle.text = crime.title
            binding.crimeDate.text = crime.date.toString()
            binding.crimePolice.text = crime.needPolice.toString()

            binding.root.setOnClickListener{
                Toast.makeText(binding.root.context,
                    "${crime.title} clicked!",
                    Toast.LENGTH_SHORT).show()
            }
        }
    }
}



class CrimeHolder(private val binding: ListItemCrimeBinding) : RecyclerView.ViewHolder(binding.root) {
    fun bind(crime: Crime){
        binding.crimeTitle.text = crime.title
        binding.crimeDate.text = crime.date.toString()

        binding.root.setOnClickListener{
            Toast.makeText(binding.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT).show()
        }
    }
}


class CrimeHolderPolice(private val binding: ListItemCrimePoliceBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(crime: Crime){
        binding.crimeTitle.text = crime.title
        binding.crimeDate.text = crime.date.toString()
        binding.crimePolice.text = crime.needPolice.toString()

        binding.root.setOnClickListener{
            Toast.makeText(binding.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT).show()
        }
    }
}

class CrimeListAdapter(private val crimes: List<Crime>): RecyclerView.Adapter<Demo>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Demo {

        // Sealed class? Need to look into it.

        /*
        if(viewType == 1){
            val inflater = LayoutInflater.from(parent.context)
            val binding = ListItemCrimePoliceBinding.inflate(inflater, parent, false)
            return CrimeHolderPolice(binding)
        }
         */

        val inflater = LayoutInflater.from(parent.context)
        val binding = ListItemCrimeBinding.inflate(inflater, parent, false)
        return Demo.CrimeHolder(binding)
    }

    override fun onBindViewHolder(holder: Demo, position: Int) {
        val crime = crimes[position]
        Demo.CrimeHolder.bind(crime) // Cannot access bind function
    }

    override fun getItemCount() = crimes.size

    override fun getItemViewType(position: Int): Int {
        return when(crimes[position].needPolice){
            true -> 1
            else -> 2
        }
    }
}

I tried like this using multiple constructor of CrimeHolder class and it worked

class CrimeHolder : RecyclerView.ViewHolder {

    private lateinit var binding: ListItemCrimeBinding
    private lateinit var bindingPolice: ListItemCrimePoliceBinding

    constructor(binding: ListItemCrimeBinding): super(binding.root) {
        this.binding = binding
    }

    constructor(binding: ListItemCrimePoliceBinding): super(binding.root) {
        this.bindingPolice = binding
    }

    fun bind(crime: Crime) {
        binding.crimeTitle.text = crime.title
        binding.crimeDate.text = crime.date.toString()

        binding.root.setOnClickListener {
            Toast.makeText(
                binding.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT
            ).show()
        }
    }

    fun bindPolice(crime: Crime) {
        bindingPolice.crimeTitle.text = crime.title
        bindingPolice.crimeDate.text = crime.date.toString()

        bindingPolice.root.setOnClickListener {
            Toast.makeText(
                bindingPolice.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT
            ).show()
        }
    }
}