0

应用已崩溃,正在执行 CustomActivityOnCrash 的 UncaughtExceptionHandler

com.airbnb.epoxy.ImmutableModelException: The model was changed between being added to the controller and being bound

控制器类

class SortFilterController @Inject constructor(
    private val schedulersFacade: SchedulersFacade,
    private val generateMapOfCategoryFilters: GenerateMapOfCategoryFilters
) : EpoxyController() {

    private val tapCountryRelay: PublishRelay<TopsProductFilter> = PublishRelay.create()

    var sortFilterViewState: SortFilterViewState = SortFilterViewState()
        set(value) {
            field = value
            requestModelBuild()
        }

    var sortFilterType: SortFilterType = SortFilterType.ALL
        set(value) {
            field = value
            requestModelBuild()
        }

    override fun buildModels() {
        sortFilterViewState.let { sortFilterViewState ->
            sortFilterViewState.filterTypes?.forEach { topsProductFilter ->
                when (SortFilterType.getId(topsProductFilter.attributeCode)) {
                    SortFilterType.COUNTRY -> {
                        CountryItemModel_()
                            .id(UUID.randomUUID().toString())
                            .tapCountryChipRelay(tapCountryRelay)
                            .countryFilter(topsProductFilter)
                            .listOfPreSelectedCountryFilters(sortFilterViewState.listOfCurrentlySelectedCountryItems ?: emptyList())
                            .addTo(this)
                    }
                }
            }
        }
    }

    val bindTapCountryRelay: Observable<TopsProductFilter> = tapCountryRelay.hide()
}

// 模型类

@EpoxyModelClass(layout = R.layout.list_item_country_item)
abstract class CountryItemModel : EpoxyBaseModel() {

    @EpoxyAttribute
    lateinit var tapCountryChipRelay: PublishRelay<TopsProductFilter>

    @EpoxyAttribute
    lateinit var countryFilter: TopsProductFilter

    @EpoxyAttribute
    lateinit var listOfPreSelectedCountryFilters: MutableList<TopsProductFilterItem>

    override fun bind(holder: EpoxyBaseViewHolder) {
        with(holder.itemView) {
        // snippet here 
      }
   }
}

在 DialogFragment oncreate 中,我设置了epoxyRecyclerView。

 epoxyRecyclerView.setController(sortFilterController)
    epoxyRecyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)

并调用控制器上的设置器并请求模型构建

sortFilterController.sortFilterViewState = sortFilterViewState
sortFilterController.sortFilterType = SortFilterType.ALL

但是,问题是我想用一些新数据更改模型中显示的数据。因此,当用户点击一个国家/地区时,我想再次设置设置器。

  private fun onTapClearAll() {
       // sortFilterViewState has some new data so I want to set it again for display.
       
       // This calling these resulted in a crash as the models epoxy attributes have changed with this new data
       sortFilterController.sortFilterViewState = sortFilterViewState
       sortFilterController.sortFilterType = SortFilterType.ALL    
    }
    
// Then I tried to do the same with a interceptor but again the app will crash.
  private fun onTapClearAll() {
            sortFilterController.addInterceptor(object : EpoxyController.Interceptor {
                override fun intercept(models: MutableList<EpoxyModel<*>>) {
                    val countryModel = models[0] as CountryItemModel_

                    countryModel.listOfPreSelectedCountryFilters(sortFilterViewState.listOfCurrentlySelectedCountryItems)
                }
            })
            sortFilterController.requestModelBuild()
        }
4

3 回答 3

2

请查看 Epoxy 的文档:https ://github.com/airbnb/epoxy/wiki/Epoxy-Controller

环氧树脂要求模型是不可变的,这意味着您不能更改模型仍然是环氧树脂建筑,那将崩溃。

简单的解决方案是,复制您的:

var sortFilterViewState: SortFilterViewState = SortFilterViewState()
        set(value) {
            field = value
            requestModelBuild()
        }

    var sortFilterType: SortFilterType = SortFilterType.ALL
        set(value) {
            field = value
            requestModelBuild()
        }

到一个数据类。并使用copy您要应用的新过滤器的功能,然后requestModelBuild使用TypedEpoxyController

建议:

data class Sort(
  val filterViewState: SortFilterViewState = SortFilterViewState(),
  val filterType: SortFilterType = SortFilterType.ALL
)
class YourEpoxyController constructor(...): TypedEpoxyController<Sort>() {
....

  override fun buildModels(data: Sort) {
    // Build model with sort here
  }
}

Your AcitivityorFragment中,排序数据发生变化的地方,让我们做:

 fun applySort(filterViewState: SortFilterViewState, filterType: SortFilterType) {
    val newSort = sort.copy(...) // sort maybe in your view_model class
    epoxyController.setData(newSort)
}
于 2021-10-15T05:17:06.437 回答
2

问题

我不是 Epoxy 专家,但 Epoxy 模型在添加到控制器后不得修改。这意味着不仅要修改它们的直接属性,还要修改这些属性的深层内容。

在您的系统中buildModels(),您总是创建全新的CountryItemModel_对象,这很好,但是您用存储在其他地方的对象填充它们,主要是在sortFilterViewState其中并且是可变的。这在您提供的源代码中不可见,但我猜您随后修改了内容,sortFilterViewState因此您实际上也修改了已创建的模型。

例如,如果您listOfCurrentlySelectedCountryItems在创建模型后进行了修改,那么listOfPreSelectedCountryFilters现有模型也会受到影响,因为它们实际上都引用了完全相同的列表。Epoxy 禁止修改listOfPreSelectedCountryFilters,因为它是模型的一部分。

技术细节

Epoxy 使用hashCode()equals()来检测模型中的变化。大多数类通过委托hashCode()/equals()它们的属性来实现这些方法,因此可以深入检测到更改。例如,如果我们有一个包含一些对象的列表,并且我们只修改了其中一个项目中的一个属性,那么hashCode()这个项目的属性将会改变,hashCode()整个列表的属性也会改变。如果我们添加新项目、删除一些项目或重新排序项目也是如此 -hashCode()列表将发生变化。如果这样的列表是 Epoxy 模型的一部分,这将被检测为模型中的更改,并将导致运行时错误。

这在 Epoxy wiki herehere中有详细描述。

解决方案

解决此问题的正确方法是在将模型添加到控制器后不要对其进行修改。使模型深度不可变可能会有所帮助,例如,确保 和 的所有属性是,TopsProductFilter而不是,如果其中一些属性包含对象,那么也使这些对象不可变。如果某些对象不能是不可变的,则在提供给模型时创建一个副本。例如,不是直接提供给模型,而是提供创建副本。请注意,这是一个浅拷贝,因此如果对象是可变的,那么您还需要创建所有项目的副本。TopsProductFilterItemvalvarlistOfCurrentlySelectedCountryItemslistOfCurrentlySelectedCountryItems.toMutableList()TopsProductFilterItem

PublishRelay尤其可疑。环氧树脂模型用于保存相对简单的数据,中继远不止于此。甚至很难具体说明它已经改变或没有改变是什么意思。我猜想通过 Epoxy 进行区分时可能会导致混乱。为什么在模型中需要这个?

还有更简单的“解决方案”。您可以通过设置validateEpoxyModelUsage来禁用模型验证false。这在配置中进行了描述。我想我不必添加这是非常不鼓励的。

于 2021-10-09T21:09:04.200 回答
0
        sortFilterViewState = SortFilterViewState(
            sortTypes = instantSearchFilterViewState.sortTypes,
            filterTypes = instantSearchFilterViewState.filterTypes,
            listOfCurrentlySelectedBrandItems = mainViewModel.listOfCurrentlySelectedBrandItems.value,
            listOfCurrentlySelectedCategoryItems = mainViewModel.listOfCurrentlySelectedCategoryItems.value,
            listOfCurrentlySelectedCountryItems = mainViewModel.listOfCurrentlySelectedCountryItems.value,
            listOfCurrentlySelectedPromotionItems = mainViewModel.listOfCurrentlySelectedPromotionItems.value,
            listOfCurrentlySelectedLifestyleBenefitItems = mainViewModel.listOfCurrentlySelectedLifestyleBenefitItems.value,
            categoryLevel = mainViewModel.categoryLevel.value
        )

        sortFilterController.sortFilterViewState = sortFilterViewState

如果该类中的以下属性之一发生更改,并且您尝试重新加载环氧树脂,则会引发异常。由于 SortFilterViewState 将具有不同的哈希码,该哈希码将与环氧树脂首次构建模型时进行比较。

解决方案是取出所有属性并将它们分别传递给环氧树脂控制器

        sortFilterController.sortTypes = instantSearchFilterViewState.sortTypes ?: emptyList()
        sortFilterController.filterTypes = instantSearchFilterViewState.filterTypes ?: emptyList()
        sortFilterController.listOfCurrentlySelectedCountryItems = mainViewModel.listOfCurrentlySelectedCountryItems.value?.toList() ?: emptyList()
        sortFilterController.listOfCurrentlySelectedBrandItems = mainViewModel.listOfCurrentlySelectedBrandItems.value?.toList() ?: emptyList()
        sortFilterController.listOfCurrentlySelectedLifestyleBenefitItems = mainViewModel.listOfCurrentlySelectedLifestyleBenefitItems.value?.toList() ?: emptyList()
        sortFilterController.listOfCurrentlySelectedPromotionItems = mainViewModel.listOfCurrentlySelectedPromotionItems.value?.toList() ?: emptyList()
        sortFilterController.listOfCurrentlySelectedCategoryItems = mainViewModel.listOfCurrentlySelectedCategoryItems.value?.toList() ?: emptyList()
        

在控制器中你会有这样的东西

var listOfCurrentlySelectedCountryItems: List<TopsProductFilterItem> = emptyList()
    set(value) {
        field = value
        requestModelBuild()
    }

var listOfCurrentlySelectedBrandItems: List<TopsProductFilterItem> = emptyList()
    set(value) {
        field = value
        requestModelBuild()
    }

var listOfCurrentlySelectedPromotionItems: List<TopsProductFilterItem> = emptyList()
    set(value) {
        field = value
        requestModelBuild()
    }

等等

于 2021-10-25T06:27:00.467 回答