A field is bound to a bean property using Binder. The same field also has a valueChangeListener. In the code below, when the value of the combobox changes the value change listener is invoked before the setter on the Data object. I think it is wrong. However, if the lines of code that registers the listener and the setBean call are reversed the setter on the Data object will be called before the listener (correct behavior)
In this version of the code, the listener method ic invoked before the setValue method:
[code]
public class ValueChangeView extends VerticalLayout implements View {
public class Data {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
private Data data;
@Override
public void enter(ViewChangeEvent event) {
data = new Data();
Binder binder = new Binder<>(Data.class);
NativeSelect combo = new NativeSelect<>(“Select”);
String items = { “One”, “Two” };
combo.setItems(items);
addComponent(combo);
Label comboValue = new Label();
addComponent(comboValue);
binder.forField(combo).bind(“value”);
}
}
[/code]In the following version, the setValue method is called before the listener:
public void enter(ViewChangeEvent event) {
data = new Data();
Binder<Data> binder = new Binder<>(Data.class);
NativeSelect<String> combo = new NativeSelect<>("Select");
String items = { "One", "Two" };
combo.setItems(items);
addComponent(combo);
Label comboValue = new Label();
addComponent(comboValue);
binder.forField(combo).bind("value");
binder.setBean(data);
combo.addValueChangeListener(e -> comboValue.setValue(data.getValue()));
}
[/code]This happens because of the following code in the Binder class. The listener associated with the binder is removed from the list and then added. However, if there is another listener in the list (the added value change listener) the order is reversed and the user defined listener is called before the binder's listener.[code]
private void initFieldValue(BEAN bean) {
assert bean != null;
assert onValueChange != null;
onValueChange.remove();
try {
getField().setValue(convertDataToFieldType(bean));
} finally {
onValueChange = getField() .addValueChangeListener(this::handleFieldValueChange);
}
}
I’m using Vaadin 16 and am still seeing the behavior. This should be identified as a bug; the value-changed-handler is invoked before the binder’s setter. That is, the underlying model’s value hasn’t actually been modified when the value-changed-handler is invoked.
This becomes a serious problem when your binder’s setter modifies other properties that are then referenced in the value-changed-handler. You would expect that the setter has already been invoked. See psuedo code below for reference:
MyObject myModel = new MyObject();
Select<MyProperty> slctProtocol = new Select<>();
slctProtocol.addValueChangeListener(
event -> {
System.out.println(myModel.otherProperty.toString()); // myModel.otherProperty will always be null because setProperty is called after this listener :(
});
selectBinder
.forField(slctProtocol)
.bind(MyObject::getProperty, MyObject::setProperty);
selectBinder.setBean(myModel);
public class MyObject {
private MyProperty property;
public MyOtherProperty otherProperty = null;
public void setProperty(MyProperty property){
this.property = property;
this.otherProperty = new MyOtherProperty();
}
}
I’m using Vaadin 16 and am still seeing the behavior. This should be identified as a bug; the value-changed-handler is invoked before the binder’s setter.
This is not a bug. Value change event from a field is lower level event, that happens with or without Binder.
What you need here, there are two alternatives:
To define a custom event of your own, which you can fire in setProperty method and then you can listen to that event elsewhere in your application.
Binder has binding level withValidationStatusHandler(..). But also that is not directly applicable here, since validation is executed before the setter. However, you could entertain the idea of setting the value of the other property in ValidationStatusHandler instead of setting it in the setter in case validation status is ok. This would be alternative to the creation of the custom event.
Why wouldn’t you want to change the order in which the events are fired so that the API acts as one would expect? If I’m constantly writing workarounds to get the API to work as needed, I start questioning why I am even using this framework in the first place?
Why wouldn’t you want to change the order in which the events are fired so that the API acts as one would expect?
I tried to explain it in the reply I wrote. The value change event is a lower level independent event. Binder actually works by setting internally value change listeners to bound fields. So the setter is called after Binder receives the value change event. Hence, it is impossible, that value change event could happen before setter is called.
If something should be done our side, it could be mentioning this in the documentation in the clearer way, so that developers would not have a wrong expectation about the functionality.