I have the use case, where I want to bind multiple ObservableValues onto one Observable, as I am actually not interested, what the value change is, I only need a means to get notified when it changes.
So this is the Observable binding, I came up:
BooleanBinding() {
{
super.bind(proxy.activeShipProperty());
INavigableVessel activeShip = proxy.getActiveShip();
super.bind(activeShip.getLoadBinding());
if (activeShip instanceof IShip) {
super.bind(((IShip) activeShip).travelState());
} else {
super.bind(((IConvoy) activeShip).getOrlegShip().travelState());
}
super.bind(date.dayDateBinding());
}
private boolean value = true;
@Override
protected boolean computeValue() {
value = !value;
return value;
}
};
And then this test code, to verify it works as expected:
Observable observable = contentProvider.createObservable(ENoticeBoardType.SHIP_WARE_INFO, proxy);
IntegerProperty invalidationCounter = new SimpleIntegerProperty(0);
InvalidationListener listener = observable1 -> invalidationCounter.setValue(invalidationCounter.get() + 1);
observable.addListener(listener);
// When
dayBinding.invalidate();
loadBinding.invalidate();
travelState.set(EShipTravelState.ANCHOR);
activeVessel.set(mock(IShip.class));
// Then
assertEquals(4, invalidationCounter.get());
Turns out it does not. The invalidationCounter is only increased once on the first invalidate call.
However when I handle the BooleanBinding from above as ObservableValue, I can add a ChangeListener:
ChangeListener listener = (obs, oldValue, newValue) -> invalidationCounter.setValue(invalidationCounter.get() + 1);
observable.addListener(listener);
// When
dayBinding.invalidate();
loadBinding.invalidate();
travelState.set(EShipTravelState.ANCHOR);
activeVessel.set(mock(IShip.class));
// Then
assertEquals(4, invalidationCounter.get());
This works as expected.
What I would like to know/have confirmed: The InvalidationListener is only called once, when the Observable becomes invalid, it then does not change back to valid, so that it could become valid again. With a ChangeListener however the new value is forced to be computed, hence the ObservableValue becomes valid again.
Based on this observation, is there any use case where I actually can use an Observable?
Observable is just an interface and the implementation determines, if calling invalidate triggers the listeners in a certain state. The behaviour you observe here is BooleanBinding triggering just a single invalidation update until the value is retrieved using the get() method.
The observers are expected to know that the value may have changed once the listeners have been notified.
This is an optimisation to avoid unnecessary computations. E.g. consider the following scenario: there are three BooleanPropertys b1, b2 and b3 and you're interested in b1 && (b2 || b3). In this case you can apply short circuit evaluation which does not require you to evaluate (b2 || b3), if b1 is false and evaluating (b2 || b3) does not require you to evaluate b3, if b2 is true. If b2 and/or b3 are expensive to compute, it makes the code more performant to not evaluate them, if you can avoid it, e.g. use the following implementation
BooleanBinding binding = new BooleanBinding() {
{
bind(b1, b2, b3);
}
@Override
protected boolean computeValue() {
if (!b1.get()) {
return false;
}
if (b2.get()) {
return true;
} else {
return b3.get();
}
// the above is basically a longer version of
// return b1.get() && (b2.get() || b3.get());
// to highlight the short circuiting behaviour
}
};
ChangeListeners in contrast to InvalidationListeners require the new value to be passed to them so adding any ChangeListener results in get() being called and every invalidation call triggers a call of computeValue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With