question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

comcom.airbnb.epoxy.ImmutableModelException: Model was changed before it could be diffed

See original GitHub issue

I am using TypedEpoxyController and i want to update item of my gridview. I have a model with epoxy attribute in it. When i changing data in this attribute object and calling setData on TypedEpoxyController i am getting such exception:

comcom.airbnb.epoxy.ImmutableModelException: Model was changed before it could be diffed

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.

As i see setData of TypedEpoxyController calls requestModelBuild and prevents from calling it directly. So why i am getting this exception and how i can update cell in gridview using epoxy?

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

4reactions
elihartcommented, Nov 5, 2017

I’m guessing image is some field on your model?

This doesn’t work - you can’t change the model after it is created. Another issue here is that you are starting the glide load for every single model in the list, that is hugely inefficient; you only want glide to start loading once the item is on screen.

what you should do instead is pass the image url to the model, and load it from there. That way the image is only loaded once the view is bound on screen.

We use glide as well, and set up a simple custom view that extends ImageView, takes a url, and loads the image. You can also use binding adapters (https://github.com/bumptech/glide/issues/932)

I also recommend clearing the imageview when the view is recycled.

On a separate note, you shouldn’t have to concatenate your ids like id("Andi" + it.andiId). there are several id overloads like id(String, Long) to avoid creating a new string

1reaction
elihartcommented, Aug 16, 2017

you’re right the problem is photo.setSelected(!photo.isSelected);. like the error says, you can’t change data in the model. that data must be treated as immutable.

How you structure that is up to you, but I can offer a few suggestions:

You could set up the model to have an extra attribute for whether it is selected

 @EpoxyModelClass
public class PhotoItemModel extends EpoxyModelWithHolder {

    @EpoxyAttribute Photo photo;
    @EpoxyAttribute boolean isSelected

   @Override
    public void bind(PhotoViewHolder holder) {
        super.bind(holder);
      holder.mSelectablePhotoView.setSelected(isSelected);
      holder.mSelectablePhotoView.setOnClickListener(v -> {
           EvenBus.getDefault.post(new PhotoEvent.PhotoClicked(); 
        });

Then change the controller to pass whether each item is selected. You will have to change your data structure to track that.

@Override
    public void buildModels(List  data) {
        for (Photo photo : data) {
            new PhotoItemModel_()
                    .id(photo.getPhotoId())
                    .photo(photo))
                    .isSelected(// todo)
                    .addTo(this);
        }
    }

Using kotlin data classes makes this easy

data class PhotoData(val isSelected: Boolean = false, val photo: Photo) 

Alternatively if you want to keep the isSelected field inside the Photo class you can do that, but in your bus callback you will need to create a new photo object and update your data list with it. You cannot update the existing photo object. They must be immutable. Using Kotlin data classes makes that trivial and forces you to do it correctly.

Personally I don’t think it makes sense to couple the view state of whether something is selected inside the data model representing it. It is simpler, but not a good pattern.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Epoxy updating the epoxy attributes in the models once they ...
App has crashed, executing CustomActivityOnCrash's UncaughtExceptionHandler com.airbnb.epoxy.ImmutableModelException: The model was changed ...
Read more >
EpoxyModel.isDebugValidationEnabled - airbnb/epoxy
This is used internally by generated models to do validation checking when * "validateEpoxyModelUsage" is enabled and the model is used with a...
Read more >
Epoxy: Airbnb's View Architecture on Android - Medium
Introducing Epoxy: github.com/airbnb/epoxy ... However, Epoxy adds a diffing algorithm to detect changes in models and only update views ...
Read more >
EpoxyController.java example - Javatips.net
package com.airbnb.epoxy; import android.os. ... so any updates to data can be completely finished before // the models are built. if (hasBuiltModelsEver) ...
Read more >
How to build your complex UI with Epoxy - Factory.dev
The Airbnb team designed Epoxy in 2016, and since then is upgrading. ... will have a setter and will be bound when the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found