I’m currently planning the details for the next phase of introducing reactive UI state managment in Flow: adding signal-based APIs to all the components.
The basic use case is to directly hook up various parts of a component’s configuration to UI state that is defined as a signal. Two trivial usage examples along with how they can be implemented with what’s in Vaadin 24.8:
- Set the heading text based on the name of the currently selected product.
H1 heading = new H1();
ComponentEffect.format(heading, H1::setText, "Editing: %s",
selectedProduct.map(Product::getName));
- Show one section of a form only if a checkbox is checked.
ValueSignal<Boolean> checked = new ValueSignal<>(false);
ComponentEffect.bind(section, checked, Component::setVisible);
// making the checkbox to update the signal is out of scope for this discussion
The one thing that is obvious is that there should be instance methods for those ComponenEffect helpers, e.g. section.someInstanceMethod(checked);. Components mainly centered around showing a text should also have shorthand constructors for using a signal value directly or 1…n signals with a format string: new H1("Editing: %s", selectedProduct.map(Product::getName));
Method semantics
The first open question, before we even get to the actual name of someInstanceMethod, is how this method should behave in relationship to the regular setter and multiple invocations of the method itself. Typical signal use would be to set up the binding exactly once and then not directly touch that aspect of the component instance after the initial setup. It’s typically a deveoper mistake if there’s also some line of code that does section.setVisible(true) or another invocation of section.someInstanceMethod(anySignal). There could also be an opposite perspective: if the visibility of that component is bound to the signal, then it would be convenient if section.setVisible(true); would also propagate the value to the signal.
There are thus three different options for how setVisible(true) should behave if an explicit signal binding is active:
- Update the value of the bound signal (assuming the signal is readable).
- Use the provided value and unregister the signal binding.
- Throw
IllegalStateException. You must explicitly unregister the signal binding (using aRegistrationreturned fromsomeInstanceMethodbefore setting a static falue.
Similarly for running someInstanceMethod again with a different signal instance:
- Set up another signal binding. The component uses the value from the signal that has most recently updated. Potentially confusing in some cases and very powerful in other cases.
- Set up a new signal binding after unregistering the previous signal binding.
- Throw
IllegalStateExceptionjust as forsetVisible(true).
Method naming
The other big question mark is what pattern to use for naming methods like this.
- One obvious candidate would be to stick to the same name
setVisible(true)andsetVisible(someSignal). This might be problematic if the “right” answer from the previous questions would make the methods have significantly different semantics. - Different semantics can be addressed with a different name, e.g.
bindVisible(someSignal). This does instead have a negative impact on discoverablity since it might be that not all regular setters have corresonding “bind” methods (you would use those throughComponentEffectinstead) and it would require an extra step to check if it’s available for your case. - Show the semantic difference but use a familar prefix:
setVisibleBinding(someSignal). The main drawback here is that the name is slightly more verbose.
What’s your take?
I’m leaning towards IllegalStateException in the two first cases just to help avoid mistakes even though I realize it might be annoying in some special cases. Would you agree?
I don’t have a strong sense in either direction for the naming pattern so that’s where I would be very happy to hear any insights from you.