-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invalidation Listener for FunctionBasedValidator #443
Comments
Hi, |
I'm attempting to do cross field validation. The primary observable isn't changing but another dependent one is. Depending on the value of the second observable (i.e. field), the validation error changes from an WARNING to ERROR. Because the primary observable isn't changing, the validation never refires... Let me know if you need more information. |
Ok I understand your use case. private FunctionBasedValidator(ObservableValue<T> source) {
source.addListener( (InvalidationListener) (evt -> validate( source.getValue() )));
} I'm not sure if there are any negative implications with this approach but would this solve your use case? |
Yes that would solve my issue. Be aware, that you would need to make the source an instance variable of the validator otherwise the invalidation listener will get garbage collected. In my usecase I was actually passing in a Binding that had dependencies. If you don't add the observable to the dependencies then the listener wouldn't get called twice. (i.e. Bindings.createObjectBinding( obs, dep1, dep2 ) )
|
I can report that one invalidation listener works well for my usecase. I did run into an issue with CompositeValidator that "can" be an issue. The following will create a validation message that gets stuck (i.e. the validator cannot remove it)
I ran into this scenario and it took a long time to figure out why it was occurring. The following changes to CompositeValidator prevent it:
Addition of existing messages are done AFTER the changelistener is added. To prevent recursive validation I changed FunctionBasedValidator#validate(T newValue) to
|
Any feedback on my findings with CompositeValidator? |
Sorry for the delay. I have to look at this evening |
I've played around a little with the validator but I still have problems to reproduce your original request on why the invalidation listener is needed. For example I've created this test case which uses a Binding with two dependencies as argument to the FunctionBasedValidator and it works just well: import static org.assertj.core.api.Assertions.assertThat;
...
@Test
public void test() {
StringProperty propertyA = new SimpleStringProperty("");
StringProperty propertyB = new SimpleStringProperty("");
StringBinding concat = Bindings.createStringBinding(() -> {
String a = propertyA.getValue();
String b = propertyB.getValue();
return a + b;
}, propertyA, propertyB);
Validator validator = new FunctionBasedValidator<>(concat, value -> {
if(!Objects.equals(value, "NO")) {
return ValidationMessage.error("Failed");
} else {
return null;
}
});
ValidationStatus validationStatus = validator.getValidationStatus();
assertThat(validationStatus.getMessages()).hasSize(1);
propertyA.setValue("N");
assertThat(validationStatus.getMessages()).hasSize(1);
propertyB.setValue("O");
assertThat(validationStatus.getMessages()).isEmpty();
propertyA.setValue("Y");
assertThat(validationStatus.getMessages()).hasSize(1);
} Can you point out where my misunderstanding is? Or can you give me a failing unit test that shows the problem? On your findings with CompositeValidator: I can reproduce the behaviour but, to be honest, I don't really understand them. ;-) I haven't thought that switching to InvalidationListener would make such a big difference in the behaviour. Can you try to describe what's happening and why it doesn't work? |
I will address the first part of your question about why an invalidation listener is preferred. I have modified your test case below:
I suppose the concat binding could return a Map of all observable values (or a holder object) so that the value used by the validation will always change if any of the dependencies change?
The second option "feels" a bit like I'm working around the framework (but I'm open to feedback on that). If you still see the benefit of an invalidation listener, I can explain the issue with CompositeValidator further. |
For me this kind of looks like a hack with the My conclusion is that your use case is perfectly valid but the For example we could extend the StringProperty propertyA = new SimpleStringProperty("");
StringProperty propertyB = new SimpleStringProperty("");
StringProperty propertyC = new SimpleStringProperty("");
StringBinding concat = Bindings.createStringBinding(() -> {
String a = propertyA.getValue();
String b = propertyB.getValue();
return a + b;
}, propertyA, propertyB);
ObservableRuleBasedValidator validator = new ObservableRuleBasedValidator();
ObjectBinding<ValidationMessage> rule = Bindings.createObjectBinding(() -> {
if (!Objects.equals(concat.get(), "NO")) {
if ("Y".equals(propertyC.getValue())) {
return ValidationMessage.warning("Warning - Failed");
} else {
return ValidationMessage.error("Error - Failed");
}
} else {
return null;
}
});
validator.addRule(rule); Notice that I've removed the What do you think of this API? Regarding the CompositeValidator: I think the source of this problem is that you have the validation status as dependency of the validator. This way it's like a cyclic dependency. |
I think you hit the nail on the head! The only reason I used FunctionBasedValidator is so that I could create my own validation message. The suggested API looks good :) You analyzed the problem with the CompositeValidator correctly although it only occurs with an invalidation listener with a cyclic dependency but the fix is simple as described above (add existing messages are done AFTER the changelistener is added). |
I've extended the I still need to update the documentation in the wiki and need to fix the problem with the cyclic validation. |
I needed to modify the following to make it work correctly:
In the situations where the status of the message changed, the old message was not getting removed. Also, it would be nice to have a helper method (or an overloaded constructor) so that I can define the rule in one line:
Thanks, |
Hi Grant, I've changed the implementation according to your comment. And I've added a constructor for convenience. You can directly add one or multiple rules. Thanks for the good ideas and for testing. |
…ator add possibility for more complex rules to observables validator #443
Did the problem with cyclic validation get fixed under this? I put quite a bit of effort into tracking it down when it occurred for me. I would hate for it to get lost :( |
Hi Grant, sorry, I've missed this. I've created a new issue #457 and have created a test case that can reproduce this issue. As none of our existing validators are leading to this issue I've created a new hypothetical validator with an However, the proposed fix for |
To enable validation logic to fire when dependencies of the source ObservableValue are invalidated for a FunctionBasedValidator, it would be useful to be able to add an InvalidationListener as below:
I have tried extending the class but have no access to the validate method.
I tried making my own ValidationClass re-using the existing logic, but the ValidationStatus class hides #addMessage and #clearMessage.
Thanks,
Grant
The text was updated successfully, but these errors were encountered: