I'm referring to https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder.html#getAdapterPosition().
In particular, the readme suggests we should save click listeners as fields to reuse with each model. How do you get a reference to the model from a re-usable click listener?
There is no direct equivalent. getAdapterPosition is used on the view holder, which doesn't apply with Epoxy. What's the context of how you want to use it. Do you have a code sample?
You can also not have a reusable listener and do @EpoxyAttribute(hash=false) so that the changing the listener doesn't affect the model's diff state
What's the recommended way to access the model from a click listener?
On Tue, Oct 11, 2016, 12:13 AM Eli Hart [email protected] wrote:
There is no direct equivalent. getAdapterPosition is used on the view
holder, which doesn't apply with Epoxy. What's the context of how you want
to use it. Do you have a code sample?You can also not have a reusable listener and do
@EpoxyAttribute(hash=false) so that the changing the listener doesn't
affect the model's diff state—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/airbnb/epoxy/issues/60#issuecomment-252807111, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAjAhqmDJYfc_KLihBHiAPub3RCdDbS4ks5qywz8gaJpZM4KTNvj
.
There isn't one right way, it depends on what data you need and personal preference. It's very flexible, you would just set up the interface you want. This example doesn't reference the model directly in the click listener, it just needs to know about what data the model is based on. Inside the model the listener is set to hash=false so that changing the listener doesn't trigger an update. This is safe since any click listener will still point to the same saveVoteForCurrentUser method.
models.add(new WLVotingRowModel_()
.upVoteCount(listing.getUpVotes().size())
.downVoteCount(listing.getDownVotes().size())
.id(listing.getId())
.listener(new WLVotingClickListener() {
@Override
public void onVoteChanged(Vote vote) {
saveVoteForCurrentUser(listing, vote);
}
@Override
public void onVoteCountClicked() {
adapterInterface.showVotesForListing(listing);
}
}));
If you want the listener to be a reusable and you can define a custom interface like this
public interface OnModelClickListener {
void onModelClicked(EpoxyModel model);
}
private final OnModelClickListener modelClickListener = model -> {
};
new MyModel_().clickListener(modelClickListener);
public class MyModel extends EpoxyModel<View> {
@EpoxyAttribute(hash=false) OnModelClickListener clickListener;
@Override
public void bind(View view) {
super.bind(view);
view.setOnClickListener(v -> clickListener.onModelClicked(MyModel.this));
}
@Override
protected int getDefaultLayout() {
return 0;
}
}
In my experience it is best to make specific interfaces or listeners for the model or data you need. It is very flexible and use case specific, that is why we don't provide a general solution.
Does that make sense?
Closing for now. Feel free to open up again if you have more questions
@elihart from your comment, do you think getting data from an edit text like this is a good approach -
In my Epoxy Adapter -
public interface OnEditTextListener {
void onTextEntered(String text);
}
private final OnEditTextListener listener = new OnEditTextListener() {
@Override
public void onTextEntered(String text) {
Timber.d(text);
}
};
EditTextModel editTextModel = new EditTextModel();
editTextModel.setEditTextListener(listener);
Then in the EditTextModel -
@EpoxyAttribute(hash = false)
InputFieldAdapter.OnEditTextListener editTextListener;
@Override
public void bind(EditTextHolder holder) {
super.bind(holder);
holder.textInputEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
editTextListener.onTextEntered(s.toString());
}
});
}
Also how to maintain a clean adapter if we follow this. Suppose i have one edit text, one radio, one checkbox field then should i define all the listeners like this in the same adapter?
Edit - Just gave this a rethought, this seems like a good use case for a event bus. Instead of passing listener from adapter to model, maybe we can pass event directly to our fragment from the Model
What you have definitely works. If all you need is the string that was entered you could skip the interface and just pass the text watcher from the adapter to the model directly. If you need other information about the model then the interface is necessary.
I think passing the event directly to the fragment is a good approach. In my opinion an event bus is a bit heavy handed for that since it exposes the events globally. What I like to do is to have an interface in the fragment or adapter that defines all the callbacks the fragment will need to know about. Something like:
public interface MyAdapterInterface {
void onSearchTextEntered(String text);
void onSearchPressed();
void onSavedSearchClicked(SavedSearch search);
}
The fragment would implement this interface and pass it to the adapter. If your models are very specific to your fragment you could pass the interface directly to the model, otherwise if they are generic models you can wrap the interface in a click listener
models.add(new TextInputModel_()
.hint(...)
.onEnter(v -> fragmentInterface.onSearchTextEntered(((EditText)v).getText())));
There's a lot of options here and not a one size fits all solution. Let me know if you have more questions or specific examples.
I will try to update the sample app with examples of different ways to use listeners with models.
@elihart thank you for your response. I went with the event bus since there a bunch of things i'm updating across fragments when text is entered. I have already implemented radio, checkbox, expandable items. Just a bit crunched on time right now. Will definitely send in a pull request to add more examples to the sample app.
Great, hope you can get everything working without much trouble. let me know if you have any other questions on best practices to get your adapter set up.
A PR to add examples to the sample app would be much appreciated :)
@elihart How to get the position of the item clicked with the PagedListEpoxyController<T>? The position i am getting with the OnModelClickListener<T extends EpoxyModel<?>, V> is sometimes wrong. Any suggestions?
@doomtrooper By its nature, the paging library doesn't necessarily have all items loaded. The adapter position is only for items loaded into the recyclerview. It might be more accurate if you use placeholders, but even then if you start at the end of the list the paged list isn't going to load the beginning. Best bet is probably to include the item position as a prop on the model if you really need the position in the paged list
Most helpful comment
There isn't one right way, it depends on what data you need and personal preference. It's very flexible, you would just set up the interface you want. This example doesn't reference the model directly in the click listener, it just needs to know about what data the model is based on. Inside the model the listener is set to
hash=falseso that changing the listener doesn't trigger an update. This is safe since any click listener will still point to the samesaveVoteForCurrentUsermethod.If you want the listener to be a reusable and you can define a custom interface like this
In my experience it is best to make specific interfaces or listeners for the model or data you need. It is very flexible and use case specific, that is why we don't provide a general solution.
Does that make sense?