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

Viewstate is not saved while using AsyncEpoxyController #1264

Open adiazarya100 opened 2 years ago

adiazarya100 commented 2 years ago

We have a vertical Epxoy recycler view with shouldSaveViewState enabled. When the user clicks on an item (EpoxyModelWithHolder) we react to the click by changing the layout's views. As an example, if the EpoxyModelWithHolder has 3 cards (views) inside and the user clicks on one of them, after the user clicks the view color will change from white to red.

When the user scrolls down, to bound new elements, we have noticed that the Epoxy recycler view rebounds the view with the previous views for example view holder number 1 and view holder number 5 will have the same "red card" selected. As far as I understand from the docs the Epoxy recycler should keep the views state after the user modify the layout/view.

"Saving view state is useful for cases where the view is modified by the user, such as checkboxes, edit texts, expansion/collapse, etc. These can be considered transient state that the model doesn't need to know about." Link to docs

Each model has a unique id. We are using epoxy:4.6.2

class SorbetWallEpoxyController(val listItems: List<SorbetComponent>) : SorbetAsyncEpoxyController() {

  override fun buildModels() {
    listItems.forEachIndexed { index, it ->
      buildComponents(index, it)
    }
  }

  fun buildComponents(index: Int, item: SorbetComponent) {
    when (item.type) {
      TWO_CARDS_COMPONENT -> addSorbetBasicQuery(index, item, R.layout.two_cards)
      THREE_CARDS_COMPONENT -> addSorbetBasicQuery(index, item, R.layout.three_cards)
    }
  }

  private fun addSorbetBasicQuery(index: Int, sorbetWallQueryItem: SorbetComponent, layout: Int) {
    basicSorbetQuery {
      id(sorbetWallQueryItem.id)
      layout(layout)
      sorbetWallItem(sorbetWallQueryItem)
    }
  }
}

Any help will be welcome :)
Thanks.

elihart commented 2 years ago

When shouldSaveViewState is enabled Epoxy simply saves the view's state as a bundle, and restores it to the view with the same id when it is rebound. It sounds likely that your view's saving/restoring state logic or binding logic isn't quite right

adiazarya100 commented 2 years ago

"It sounds likely that your view's saving/restoring state logic or binding logic isn't quite right"

Let me elaborate about our saving/restoring state logic and binding logic: "saving" => shouldSaveViewState is enabled "restoring" => nothing because the shouldSaveViewState is enabled. Is there something else we need to do?

"binding logic" => We bind the view according to the @EpoxyAttribute we have. Let's say the @EpoxyAttribute is a class:

class Card {
  var selectedCard: Int = -1
}

After the user click on a card we change the selectedCard to the index of the card (we have a maximum of 3 cards so the value can be: 1, 2, or 3) The bind function looks for the selectedCard value and sets the view of the selected Card to be red.

is our binding logic correct according to the example above?

elihart commented 2 years ago

it sounds like you're confusing view saved state and bound data. view saved state is not at all related to bound data, and is separate and entirely internal to the view. the view implements it like in https://medium.com/super-declarative/android-how-to-save-state-in-a-custom-view-30e5792c584b