Skip to content

Kotlin Model Examples

Eli Hart edited this page Jul 28, 2018 · 3 revisions

Epoxy models can be written in Kotlin easily. However, the annotations Epoxy was built with were designed to generate code as workarounds for Java limitations, which don't work as nicely in a Kotlin world.

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

This approach uses a more idiomatic Kotlin approach and does not require annotations or annotation processing. The data class is required to generated 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.

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)
    }
}