airbnb / epoxy

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

Epoxy Model fail to detect data difference? #1019

Open hctangaa opened 4 years ago

hctangaa commented 4 years ago

Background I am doing a messaging app. The message sdk I used return the messages in a paged format and thats why I am use the PagedListEpoxyController.

Inside a chat room, user can chat and also select multiple messages to copy or delete (just like whatsapp or telegram)

Current Situtation

(Part of the codes)

class MessageController : PagedListEpoxyController<ChatMessage>() {

companion object {
    val TAG = MessageController::class.java.simpleName
}

var selectionState by observable(SelectionState(), this::rebuildList)
var callbacks: Callbacks? by observable(null, this::rebuildList)

interface Callbacks {
    fun onItemLongPress(chatMessage: ChatMessage)
}

private fun rebuildList() {
    super.requestForcedModelBuild()
}

override fun buildItemModel(currentPosition: Int, item: ChatMessage?): EpoxyModel<*> {
    if (item == null) {
        return LoadingRowModel_().id("loadingRow")
    } else {
        (some logic to check if other model is built)
        TextMessageSelfRowModel_().apply {
            id(item.getMessageId())
            timestamp(item.getTimeStamp())
            message(item.getTextMessage())
            isSelectionMode(selectionState.isSelectionMode)
            onItemLongPressListener(View.OnLongClickListener {
                callbacks?.onItemLongPress(item)
                return@OnLongClickListener false

When I long press the item, its fire a event to update the state and then after the state is updated, it trigger rebuildList(). For the log, I can see that the rebuildList() is successfully called (i.e. selectionState.isSelectionMode is updated) but the model is not updated accordingly. I believed that it is because old and newly built model has the same hashCode and equal() = true.

I am just wonder why even selectionState.isSelectionMode is updated, while the epoxy did not detect any difference.

TextMessageSelfRowModel_() is a generated model generated from TextMessageSelfRow

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

init {
    inflate(context, R.layout.list_item_text_message_self, this)
}

@TextProp
fun setTimestamp(timestamp: CharSequence) {
    tvTimestamp.text = timestamp
}

@TextProp
fun setMessage(message: CharSequence) {
    tvMessage.text = message
}

@ModelProp
fun setIsSelectionMode(isSelectionMode: Boolean) {
    rbSelect.isVisible = isSelectionMode
}

@CallbackProp
fun setOnItemLongPressListener(onLongClickListener: OnLongClickListener?) {
    this.setOnLongClickListener(onLongClickListener)
}
}
amrro commented 4 years ago

Quoting from the docs:

Be aware that since this applies the option {@link com.airbnb.epoxy.ModelProp.Option#DoNotHash} changing the value of the listener will not trigger an update to the view.

In other words, updating the model won't update the callback.

Workaround: set the model directly in the ModelView, and use it in the callback.

hctangaa commented 4 years ago

Quoting from the docs:

Be aware that since this applies the option {@link com.airbnb.epoxy.ModelProp.Option#DoNotHash} changing the value of the listener will not trigger an update to the view.

In other words, updating the model won't update the callback.

Workaround: set the model directly in the ModelView, and use it in the callback.

I think my problem does not really relate to the callback. Because I did not update the callback, the callback is always the same callback and could be hashed.

Simply asking, my question is i am not able to trigger force model build inside PagedListEpoxyController when the dataset in unchanged. While the changing parameter is not inside the dataset (i.e. for example when toggling edit mode, the dataset is still unchanged while one a param in the controller changed. )

stankinzl commented 2 years ago

I have a similar issue. My epoxy stops calling bind functions (neither of them) for anything else than a change in one EpoxyAttribute in my EpoxyModelWithHolder class after that attribute is changed the first time. In my case, it's editMode attribute that on bind() triggers all items to animate and transition using MotionLayout (I tried commenting that one out completely and the issue still remains). It's super weird and I cannot find a solution and I've been looking for eternity. This seems a similar issue.

I set the data, buildModels() gets called with different data set, but bind functions of the model are no longer called after I change that one attribute to true (editMode) for the first time, and the bind is only called when that attribute is changed later on and not in any other case... There is a bunch of other EpoxyAttributes that I want to change later with new buildModels() calls but it no longer reacts to the changes.