Hi, what is the best way to retrieve/validate input data from views (like EditText) when an event (button click) is triggered?
In my specific case, I have something like this:
public abstract class MyFormModel extends EpoxyModelWithHolder<MyFormModel.ViewHolder> {
//I have to define this property for reusing it on validate method.
private ViewHolder mHolder;
@Override
public void bind(MyFormModel.ViewHolder holder) {
mHolder = holder;
//...
}
//this method is called from controller -> fragment
public void validate() {
mHolder.etFirstName.setError("First name is required");
}
}
Is this approach correct? How can I improve it?
Thanks!
(Amazing library!!!)
There are a few ways to do this. I generally don't recommend doing it the EpoxyModel itself though since it can get messy and confusing. Using callbacks and interfaces is usually cleaner.
Here are three ways I can think to do this:
First, a listener is set on the textviews that calls back to the controller whenever input text is changed. The controller stores the current text. It can either validate it as the text is changed, or wait until later. It can then update the model with an error string/flag which the view can then show. This is probably most similar to how we do things in our app. It is a nice approach since it is very clear what is happening, the controller always has the current data, and not much logic is not in the epoxy model. You can make a custom view to handle all this instead of a viewholder to ena
public abstract class MyFormModel extends EpoxyModelWithHolder<MyFormModel.ViewHolder> {
@EpoxyAttribute String error;
@EpoxyAttribute TextWatcher textWatcher;
@Override
public void bind(MyFormModel.ViewHolder holder) {
holder.textView.addTextWatcher(textWatcher);
if (!error.isEmpty()) {
holder.textView.setError(error);
}
}
}
Another approach is to pass some sort of object to the model that allows you to register a validation callback. When the controller wants validations to be performed the callback in the bind method is called and you can do whatever validations you want.
public abstract class MyFormModel extends EpoxyModelWithHolder<MyFormModel.ViewHolder> {
@EpoxyAttribute(DoNotHash) ValidationRegistry validationRegistry;
@Override
public void bind(MyFormModel.ViewHolder holder) {
validationRegistry.registerValidationCallback(() -> {
// check if my views are valid and show errors if necessary
})
}
}
This last approach is a bit of a mix of the first two. The model listens for text changes and whenever the text is changed it calls back to the controller asking it to validate the current text.
public abstract class MyFormModel extends EpoxyModelWithHolder<MyFormModel.ViewHolder> {
@EpoxyAttribute(DoNotHash) ValidationCallback validationCallback;
@Override
public void bind(MyFormModel.ViewHolder holder) {
textView.onTextChanged(newText -> {
if (validationCallback.validate(newText)) {
// handle not valid case
}
}
}
}
Which approach you take is going to really depend on what your UI needs are, that's why epoxy doesn't try to solve this in a generic way and leaves it flexible. The downside to the second to approaches is that they only work when the model is bound. If you have a long list of items and scroll to the bottom, then the top views don't exist anymore and can't be validated. The first approach solves that by storing the current input data in the controller. For that reason it is probably the most robust, and I think it is a nice pattern to have the controller maintain all current data, instead of some of it living on the views.
Two things to watch out for - make sure you remove all listeners in unbind, and if you go with the first approach you will need to rebuild models whenever text changes (assuming you are using EpoxyController). Since it is excessive and unnecessary to rebuild models when edittext input changes (since the view is already up to date), you can use requestDelayedModelBuild and set a delay of a few hundred milliseconds
@elihart Awesome!! I will try it. Thank you so much.
Most helpful comment
There are a few ways to do this. I generally don't recommend doing it the EpoxyModel itself though since it can get messy and confusing. Using callbacks and interfaces is usually cleaner.
Here are three ways I can think to do this:
First, a listener is set on the textviews that calls back to the controller whenever input text is changed. The controller stores the current text. It can either validate it as the text is changed, or wait until later. It can then update the model with an error string/flag which the view can then show. This is probably most similar to how we do things in our app. It is a nice approach since it is very clear what is happening, the controller always has the current data, and not much logic is not in the epoxy model. You can make a custom view to handle all this instead of a viewholder to ena
Another approach is to pass some sort of object to the model that allows you to register a validation callback. When the controller wants validations to be performed the callback in the bind method is called and you can do whatever validations you want.
This last approach is a bit of a mix of the first two. The model listens for text changes and whenever the text is changed it calls back to the controller asking it to validate the current text.
Which approach you take is going to really depend on what your UI needs are, that's why epoxy doesn't try to solve this in a generic way and leaves it flexible. The downside to the second to approaches is that they only work when the model is bound. If you have a long list of items and scroll to the bottom, then the top views don't exist anymore and can't be validated. The first approach solves that by storing the current input data in the controller. For that reason it is probably the most robust, and I think it is a nice pattern to have the controller maintain all current data, instead of some of it living on the views.
Two things to watch out for - make sure you remove all listeners in
unbind, and if you go with the first approach you will need to rebuild models whenever text changes (assuming you are using EpoxyController). Since it is excessive and unnecessary to rebuild models when edittext input changes (since the view is already up to date), you can userequestDelayedModelBuildand set a delay of a few hundred milliseconds