androidkotlinepoxy

How do I properly initialize a property in Epoxy ModelView when view state saving is enabled?


I have following code for the model view.

When I disable saveViewState = true or remove it, checkbox?.isChecked is properly set by epoxy adapter (to true or false, according to isChecked boolean that is passed to the annotated method). But, when I enable it (set saveViewState = true), the checkbox?.isChecked value is always set to false (at least as I see it in the UI, all checkboxes are unchecked).

I've put logs before this.checkbox?.isChecked = isChecked and after, and I saw that the value passed is correct, and checkbox isChecked property is set correctly. What I don't understand is why epoxy overrides all that and sets checkbox to an unchecked state (to false), despite having its property set, for example, to a checked state. I've tried to do a requestModelBuild on the epoxy view right after models were built, and with some delay, but it didn't help.

@ModelView(saveViewState = true)
class RowView: ConstraintLayout {
    constructor(context: Context):
        super(context)
    constructor(context: Context, attributeSet: AttributeSet):
        super(context, attributeSet)
    constructor(context: Context, attributeSet: AttributeSet, styleAttr: Int):
        super(context, attributeSet, styleAttr)

    @TextProp
    fun setText(text: CharSequence) {
        this.checkbox?.text = text
    }

    @ModelProp
    fun setCheckedState(isChecked: Boolean) {
        this.checkbox?.isChecked = isChecked
    }

    @CallbackProp
    fun setOnChangeListener(listener: CompoundButton.OnCheckedChangeListener?) {
        listener?.let { this.checkbox?.setOnCheckedChangeListener(it) }
    }
}

How do I set checkbox state inside epoxy model view when view state is enabled? Does this issue also happens when EditText is used? And why checkbox label is populated correctly (no empty text, the text passed is shown as it should be)?


Solution

  • As discussed in https://github.com/airbnb/epoxy/issues/681, the property states should be stored elsewhere, and onChangeListener should request model rebuild at the end. i.e "you can't have data provided both by saved state and by a model prop since they conflict, saved state overrides model prop settings".

    For this to work, I had to change

    @ModelView(saveViewState = true)
    class RowView: ConstraintLayout {
    

    to

    @ModelView
    class RowView: ConstraintLayout {
    

    and implement model rebuild like this

    view.rv.buildModelsWith { controller ->
        model.items.forEach { item ->
            RowViewModel_().id(item.id.name)
                .checkedState(model.itemsChosen[item] ?: false)
                .onChangeListener { buttonView, isChecked ->
                    if (buttonView.isShown && buttonView.isPressed) {
                        model.itemsChosen[item] = isChecked
                        controller.requestModelBuild()
                    }
                }
                .addTo(controller)
        }
    }
    

    Tested it and it worked.