0

I have an abstract class:

abstract class AbstractViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

And several childs:

class LabelViewHolder(itemView: View) : AbstractViewHolder(itemView) {
    val name: TextView = itemView.label
}

class ButtonViewHolder(itemView: View) : AbstractViewHolder(itemView) {
    val name: TextView = itemView.button
}

In onCreateViewHolder() I want to bypass all ViewTypes with when:

return when (viewType) {
    ViewType.LABEL.id -> {
        val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.row_label, viewGroup, false)
        LabelViewHolder(view)
    }
    ViewType.BUTTON.id -> {
        val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.row_button, viewGroup, false)
        ButtonViewHolder(view)
    }

In order to not write the same for many when branches, I want to use a function like this:

return when (viewType) {
    ViewType.LABEL.id -> getNewViewHolder(viewGroup, R.layout.row_label, LabelViewHolder::class.java)
    ViewType.BUTTON.id -> getNewViewHolder(viewGroup, R.layout.row_button, ButtonViewHolder::class.java)
}

private fun getNewViewHolder(viewGroup: ViewGroup, @LayoutRes layoutRes: Int, cls: Class<out AbstractViewHolder>): AbstractViewHolder {
    val view = LayoutInflater.from(viewGroup.context).inflate(layoutRes, viewGroup, false)
    return cls.newInstance() // cls(view) is not allowed.
}

As you see, I cannot return an object of AbstractViewHolder child, because it doesn't allow me to create a class with parameter view. Is it possible to pass a class to the function and create it's object?

CoolMind
  • 26,736
  • 15
  • 188
  • 224

2 Answers2

2

Make function with generic reified param. Like this:

private inline fun <reified T> create(parent: ViewGroup, @LayoutRes layoutRes: Int): T {
    return T::class.java.constructors[0].newInstance(
            LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
    ) as T
}

and then

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder {
    return when (viewType) {
        0 -> create<LabelViewHolder>(parent, R.layout.abc_search_view)
        else -> create<ButtonViewHolder>(parent, R.layout.abc_action_menu_item_layout)
    }
}

Now you can access T java object and invoke propert consturctor. This should work in debug, for release remember to make sure proguard won't remove constructor you use.

Cililing
  • 4,303
  • 1
  • 17
  • 35
  • I faced a problem here. In `release` build we cannot use reflection and get exception: `java.lang.ArrayIndexOutOfBoundsException: length=0; index=0")`. So, if we want to use this code, we should add all created ViewHolders in "proguard-rules.pro": `-keepattributes Signature, InnerClasses, EnclosingMethod` and later: `-keep class com.package.presentation.YourAdapter$ButtonViewHolder { *; }` (and so on for all ViewHolders). – CoolMind Apr 08 '19 at 11:48
  • I can try it now, but maybe don't obfuscate all class inherited from some `AbstractViewHolder`. Or with annotation @dontobfuscate (I believe it is possible) – Cililing Apr 08 '19 at 15:38
  • Thanks, it can help. Though I returned back to create several similar classes. – CoolMind Apr 08 '19 at 15:48
  • Up there should be I can't, I don't think today and I also cannot edit last comment xD Sorry. Anyway, let me know if it worked! ;) – Cililing Apr 08 '19 at 17:11
1

See https://stackoverflow.com/a/63690450/2914140.

override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): AbstractViewHolder {
    return when (viewType) {
        LABEL -> createItem(viewGroup, R.layout.row_label, ::LabelViewHolder)
        BUTTON -> createItem(viewGroup, R.layout.row_button, ::ButtonViewHolder)
        else -> throw IllegalStateException("Wrong class")
    }
}

private fun <T> createItem(
    viewGroup: ViewGroup,
    layoutRes: Int,
    method: (View) -> T
): T {
    val view = LayoutInflater.from(viewGroup.context).inflate(layoutRes, viewGroup, false)
    return method(view) // Creates T(view).
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224