airbnb / epoxy

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

Cant update View #201

Closed e-hai closed 7 years ago

e-hai commented 7 years ago
  1. public class EpoxyActivity extends AppCompatActivity implements View.OnClickListener { private List dataList = new ArrayList<>(); private RecyclerView recyclerView; private SampleController mController;

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_epoxy); recyclerView = (RecyclerView) findViewById(R.id.epoxy_rv); mController = new SampleController(this); recyclerView.setAdapter(mController.getAdapter()); recyclerView.setLayoutManager(new LinearLayoutManager(this)); loadData(); }

    private void loadData() { for (int i = 0; i < 10; i++) { EpoxyData data = new EpoxyData(); data.setId(i); data.setSelect(false); data.setTxt("id=" + i); dataList.add(data); } updateController(); }

    private void updateController() { mController.setData(dataList); }

    @Override public void onClick(View v) { switch (v.getId()) { case R.id.model_txt_cb: selecItem(v); break; } }

    private void selecItem(View v) { EpoxyData selectData = (EpoxyData) v.getTag(); long id = selectData.getId(); for (EpoxyData item : dataList) { if (id == item.getId()) { item.setSelect(true); } } updateController(); } }

  2. public class EpoxyData { private long id; private String txt; private boolean select;

    public String getTxt() { return txt; }

    public void setTxt(String txt) { this.txt = txt; }

    public boolean isSelect() { return select; }

    public void setSelect(boolean select) { this.select = select; }

    public long getId() { return id; }

    public void setId(long id) { this.id = id; } }

  3. public class SampleController extends TypedEpoxyController<List> { private final View.OnClickListener callbacks; SampleController(View.OnClickListener callbacks) { this.callbacks = callbacks; }

    @Override protected void buildModels(List carousels) {

    for (int i = 0; i < carousels.size(); i++) {
        add(new TextModel_()
                .id(i)
                .clickListener(callbacks)
                .data(carousels.get(i)));
    }

    }

    @Override protected void onExceptionSwallowed(RuntimeException exception) { // Best practice is to throw in debug so you are aware of any issues that Epoxy notices. // Otherwise Epoxy does its best to swallow these exceptions and continue gracefully throw exception; } }

  4. @EpoxyModelClass(layout = R.layout.model_text_layout) public abstract class TextModel extends EpoxyModel { @EpoxyAttribute EpoxyData data;

    @EpoxyAttribute(DoNotHash)
    View.OnClickListener clickListener;
    
    @Override
    public void bind(TextContentView view) {
        view.update(data);
       view.selectListener(clickListener);

    } }

  5. public class TextContentView extends LinearLayout { private TextView title; private TextView select;

    public TextContentView(Context context, AttributeSet attrs) { super(context, attrs); init(); }

    private void init() { setOrientation(HORIZONTAL); inflate(getContext(), R.layout.view_txt_content, this); title = (TextView) findViewById(R.id.model_txt_tv); select = (TextView) findViewById(R.id.model_txt_cb); }

    public void update(EpoxyData data) { title.setText(data.getTxt()); select.setTag(data); if (data.isSelect()) { select.setText("select"); } else { select.setText("not select"); } }

    public void selectListener(OnClickListener listener) { select.setOnClickListener(listener); } }

elihart commented 7 years ago

It generally looks ok, I'm not sure why it wouldn't work. Can you set some breakpoints and see what is going on? Are the models being bound? Is the click listener getting called at all?

The one thing I notice is that the way you're handling the click callback is fragile and complicated. I would make an interface and pass the data object directly.

I also noticed that the id you're using on the textmodel is the position, and not the id off of the EpoxyData object.

interface DataCallbacks {
   void onItemClicked(EpoxyData data);
}

for (EpoxyData  data : carousels) {
        add(new TextModel_()
                .id(data.getId())
                 .clickListener(v -> callbacks.onItemClicked(data)
                .data(data);
    }

And in the activity implement the DataCallbacks interface

void onItemClicked(EpoxyData data)
 data.setSelected(!data.isSelected);
 updateController();
}

This isn't quite right, since it modifies the EpoxyData object directly. The TextModel has a reference to that data object, so changing the data object will change the state of the TextModel, which isn't allowed. You could have the EpoxyData object be immutable, and create a new one. Or you could set the primitive values on the TextModel (eg just the selected state and text string).

elihart commented 7 years ago

I think I just realized why your view is not updating - your EpoxyData class does not implement equals/hashCode.

Alternatively you could do this

@EpoxyModelClass(layout = R.layout.model_text_layout)
  public abstract class TextModel extends EpoxyModel<TextContentView> {
    @EpoxyAttribute
    String text;

   @EpoxyAttribute
   boolean selected;

    @EpoxyAttribute(DoNotHash)
    View.OnClickListener clickListener;

    @Override
    public void bind(TextContentView view) {
        view.update(data);
       view.selectListener(clickListener);
  }

I would recommend turning on the config option to check that every attribute implements equals and hashcode so the compiler will detect this issue for you

e-hai commented 7 years ago

@elihart thx a lot,According to code changes,it working well!

elihart commented 7 years ago

Good to hear :)