airbnb / epoxy

Epoxy is an Android library for building complex screens in a RecyclerView
https://goo.gl/eIK82p
Apache License 2.0
8.46k stars 730 forks source link

Strange ImmutableModelException #1330

Open densakh opened 1 year ago

densakh commented 1 year ago

Our crashlytics reports ImmutableModelException

com.airbnb.epoxy.ImmutableModelException: The model was changed during the bind call. Position: 2 Model: IncomingMessageEpoxyModel_{message=IncomingMessageModel(id=2, title=Title text, text=Message text, time=15:18, avatar=Local(image=2131231072))}IncomingMessageEpoxyModel_{id=71303202, viewType=2131558589, shown=true, addedToAdapter=false}

Epoxy attribute fields on a model cannot be changed once the model is added to a controller. Check that these fields are not updated, or that the assigned objects are not mutated, outside of the buildModels method. The only exception is if the change is made inside an Interceptor callback. Consider using an interceptor if you need to change a model after it is added to the controller and before it is set on the adapter. If the model is already set on the adapter then you must call `requestModelBuild` instead to recreate all models.
    at com.airbnb.epoxy.EpoxyModel.validateStateHasNotChangedSinceAdded(EpoxyModel.java:466)

The problem is (as i see) that the model was not changed during the bind call. Epoxy model has only one @EpoxyAttribute field IncomingMessageModel, which is data class with only val params

sealed class MessageModel(
    open val id: String
)

data class IncomingMessageModel(
    override val id: String,
    val title: String?,
    val text: CharSequence?,
    val time: String,
    val avatar: Image // image here is always Image.Local
) : MessageModel(id) 

sealed class Image {
    data class Remote(val image: List<ImageVariant>): Image()
    data class Local(@DrawableRes val image: Int): Image()
}

and single onBind listener which defined as val field inside controller

private val messageModelBind: (EpoxyModel<*>, Any, Int) -> Unit = { _, _, position ->
    if (position > messages.size - ITEMS_BEFORE_LOADING) loadNewPageListener.invoke()
}

which used in buildModels() method like this

is IncomingMessageModel -> {
    IncomingMessageEpoxyModel_()
        .id(index)
        .message(message)
        .onBind(messageModelBind)
        .addTo(this)
}

index here is an index in messages list which never changes for already added models (new messages added to the end of list)

I can't reproduce this error (as i see from crashlytics it constantly occures only on single device for one user with Horor 10I, right after screen open), and have no ideas what is the reason, this model used only in one controller on single screen and has unique layout id (so i think its not model caching problem or something like that)

Epoxy version is 4.4.4

viroth-ty commented 1 year ago

Make sure in your model class doesn't change value of variable