Skip to content

Kotlin Model Examples

Eli Hart edited this page Oct 18, 2018 · 3 revisions

Epoxy models can be written in Kotlin easily. It is recommended to use either the @ModelView or the ViewHolder approach with Kotlin - use @ModelView if you like custom views or have more complicated views.

This page assumes you already understand Epoxy and have read the rest of the wiki. This only adds additional context on top of that for how Kotlin usage can differ.

Here are some ideas for using Kotlin when writing models. You can adapt these and create your own patterns as well. See the kotlinsample module for more code.

With @ModelView

The ModelView annotation is used on Views to have models generated from those views. This is pretty straightforward with Kotlin, but properties need some special handling.

@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
class KotlinView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    @ModelProp
    fun setValue(value: Int) {
        // Do something here with value
    }

    // Or if you need to store data in properties there are two options

    // Can make it nullable like this and annotate the setter
    var myListener: View.OnClickListener? = null
        @CallbackProp set

    // Or use lateinit
    @TextProp lateinit var myText: CharSequence

    @AfterPropsSet
    fun useProps() {
        // This is optional, and is called after the annotated properties above are set.
        // This is useful for using several properties in one method to guarantee they are all set first.
    }
}

With View Holders

This more traditional style uses an Epoxy view holder pattern. The KotlinHolder is used to cache the view look ups, and uses property delegates to simplify the process.

The annotations allow for code generation of a subclass, which has equals/hashcode, and some other helpers. An extension function is also generated to make it easier to use this in an EpoxyController.

The holder code is provided as a sample, and you can copy it into your project and modify as needed.

@EpoxyModelClass(layout = R.layout.view_holder_page_header)
abstract class SampleKotlinModelWithHolder : EpoxyModelWithHolder<Holder>() {

    @EpoxyAttribute lateinit var title: String
    @EpoxyAttribute lateinit var imageUrl: Uri

    override fun bind(holder: Holder) {
        holder.imageView.setImageURI(imageUrl)
        holder.titleView.text = title
    }

}

class Holder : KotlinHolder() {
    val titleView by bind<TextView>(R.id.title)
    val imageView by bind<ImageView>(R.id.image)
}

Custom Kotlin Model

Not officially supported

This approach uses a more idiomatic Kotlin approach and does not require annotations or annotation processing. The data class is required to generate equals/hashcode which Epoxy needs for diffing. Views are easily declared right in the model via property delegates, so no view holder class is needed.

The downside is that views are looked up again every time the model is bound to a view, so it is not very efficient. Because of that this isn't recommended for performant apps, but is provided as a starting point or idea for what could be possible if you want to adapt it.

If you don't understand Epoxy well, or don't understand Kotlin well enough to know what this code is doing then it is recommended that you use either the @ModelView or ViewHolder approach instead.

This is based on the KotlinModel sample that you can copy into your project and adapt.

The downside is you lose some functionality that the generated code provides (like the extension function for use in the EpoxyController)

data class SampleKotlinModel(
    val title: String,
    val imageUrl: Uri
) : KotlinModel(R.layout.view_holder_page_header) {

    val titleView by bind<TextView>(R.id.title)
    val imageView by bind<ImageView>(R.id.image)

    override fun bind() {
        titleView.text = title
        imageView.setImageURI(imageUrl)
    }
}