If you are like me, when you started programming with JavaFX you found that InvalidationListeners
are a mysterious and unfathomable thing. It’s not at all clear how they work, and why you would want to use them. So you stay away, and use ChangeListener
instead because it’s pretty to understand what a “change” is, and how ChangeListener
works.
But this is probably a mistake. InvalidationListener
is badly explained in the JavaDocs, but the concepts behind them are actually pretty simple and you should learn how to use them. There’s a good chance that InvalidationListener
is a better fit for your needs that ChangeListener
.
Observable Values and Listeners
The concept of “observability” is key to any Reactive design, and in JavaFX it’s baked in right from the start, but what does it mean?
In programming, the “Observer” pattern is a construct which allows observers to be registered with a value, and those observers will be notified when that value changes. “Observable” in JavaFX turns that on it’s head, and changes the focus from the observer to the observed. It really just the same thing, but from a slightly different perspective.
In JavaFX the Observer pattern is implemented by wrapping the data values into classes which which present methods to allow other classes to be notified when the enclosed value changes. The notification mechanism is called a “Listener”.
What Are Listeners For?
For the most part, listeners are used to trigger imperative code due to changes in the State of your application. Most often, they are used to perform operations that need to be done off the FXAT, or to invoke functionality which simply isn’t designed to be Reactive. For instance, Animations
are actions that need to be triggered, and this can be done through a listener. In most cases, however, it’s better to implement a more direct trigger like an ActionEvent
if it’s possible.
Sometimes a listener can be useful when it’s too complicated to implement a Binding
. Perhaps, if there is an arbitrarily large number of elements that need to be connected in a complicated way, attempting to install a Binding
on each one may be prohibitive. In that case, having a single listener that then updates all of the dependent values might be less complicated and more clear.
Before we look at the listeners, let’s take a look at how the data values are wrapped into observables.
The JavaFX Observable Interfaces
In JavaFX, the great-great-grandfather of Properties
and Bindings
is the Observable
interface. The great-grandfather is the ObservableValue
interface. These two interfaces define the most important aspect of anything observable - that it can be listened to.
Let’s have a quick look at both of these interfaces, and see what they do.
Observable
Observable
is a top level interface, so it doesn’t inherit any methods from any other interfaces. It has just two methods:
- addListener(InvalidationListener listener)
- removeListener(InvalidationListener listener)
That’s it. Pretty simple. We’ll look at what an InvalidationListener
is in a little bit.
ObservableValue
ObservableValue
extends Observable
and adds three new methods:
- addListener(ChangeListener<? super T> listener)
- removeListener(ChangeListener<? super T> listener)
- getValue()
Okay, so ObservableValue
adds the ability to add and remove a ChangeListener
onto a value, in addition to the InvalidationListener
from Observable
. It also adds the ability to get the current value of whatever is inside the ObservableValue
.
You should also notice that Observable
has no way to get the current value of it’s object, which you’ll see makes it not that useful by itself.
ObservableValue
, though, is pretty useful if all you want is to catch when a value changes, grab the new value and do something with it. Which, to be honest, is about 90% of the use cases for any observable class.
Remember that these are just interfaces, so while you can use them as a data type you can’t actually instantiate them and for that you’ll have to use some class that implements them. Presumably, any class that you use to do this is also going to have some way to put data into the ObservableValue
and change it. We’re not going to talk about that at all in this article.
The Listeners
The two listeners are also interfaces, and they’re both fairly simple. Both of these are functional interfaces, so you’ll most likely come across them defined as lambda statements.
InvalidationListener
InvalidationListener
has only one method:
- invalidated(Observable observable)
This method will be called whenever the value in the Observable
becomes invalid, and it will pass the observed object itself to that method. This is convenient if you are going to reuse the same listener for several Observables
.
Okay, but what is it, and when is it called?
The JavaDocs are less than helpful:
An InvalidationListener is notified whenever an Observable becomes invalid. It can be registered and unregistered with Observable.addListener(InvalidationListener) respectively Observable.removeListener(InvalidationListener)
It says to look at the JavaDocs for ObservableValue
. You’ll find this:
An ObservableValue generates two types of events: change events and invalidation events. A change event indicates that the value has changed. An invalidation event is generated, if the current value is not valid anymore.
And then it goes on about “lazy evaluation” without really explaining anything.
So now we “know” that invalidation events are triggered when the value becomes “invalid”, and that change events are triggered when the value changes. Let’s look at ChangeListener
before we try to figure out what “invalid” means.
ChangeListener
ChangeListener
also has only one method:
- changed(ObservableValue<? extends T> observable, T oldValue, T newValue)
This is a little bit more clear. The method is called an it’s passed the ObservableValue
class itself, plus we get the old value and we get the new value. That makes sense.
Which Listener to Choose?
If you are like I was years ago, you got to that “becomes invalid” part in the JavaDocs and you said to yourself, “I don’t get it. ChangeListener
is the way to go, because I understand that idea”. The Web wasn’t much help either, because every site seemed to get caught up in details around lazy evaluation and “weak listeners”.
So let’s clear this up.
What Does “Invalid” Mean?
Here are the things that define what “Invalid” is all about:
-
First and foremost, “Invalid” is a state of the
Observable
. TheObservable
is either “Valid” or “Invalid”, and theObservable
itself is going to keep track of this for us. -
The most important thing to understand is that the
Observable
is always set to “Valid” whengetValue()
is called on it. That’s the only way to set it “Valid”. -
The next most important thing to understand is that it can’t became “more invalid”. If an
Observable
is already “Invalid” and something happens that would otherwise have turned it from “Valid” to “Invalid”, nothing’s going to change in theObservable
. It’s already “Invalid”. -
Finally, an
Observable
is flipped to “Invalid” when it’s possible that it’s value might have changed.
Might?
This is usually going to happen through Bindings
. In the example we’re going to look at, we’re going to have an ObservableValue<Boolean>
that is based on whether or not an ObservableValue<Integer>
is greater than 10. If the initial value of the ObservableValue
is 8 and then 1 is added to it, then your ObservableValue<Boolean>
might have changed. You’re not going to know if it has unless you run the code for the Binding
, which won’t happen until you call getValue()
on the your ObservableValue<Boolean>
.
That’s where Invalidation
comes into play. As soon as that 8 is changed to a 9, JavaFX is going to detect that your Boolean
ObservableValue
depends on it, and flag both of the ObservableValues
as “Invalid”.
In this case, calling getValue()
on the Boolean ObservableValue
is going to call it to call getValue()
on the Integer ObservableValue
, and both of them are going to get flipped back to “Valid” as a result.
An Example
Let’s take a look at how this works. Here’s a small program that puts a Button on the screen. The program has two Properties
, one is a BooleanProperty
and the other is an IntegerProperty
. The BooleanProperty
is bound to the IntegerProperty
using the “Fluent” API, and it tests whether the IntegerProperty
’s value is greater than 10. The IntegerProperty
starts off with a value of 7, and is incremented each time the Button
is clicked.
There are two InvalidationListeners
, one on each Property
. Each one simply does a System.out.println()
, with some text that identifies it, and then it invokes the getValue()
method on the IntegerProperty
to show the current value of that Property.
public class InvalidationDemo extends Application {
private final BooleanProperty overTen = new SimpleBooleanProperty(false);
private final IntegerProperty counter = new SimpleIntegerProperty(7);
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) throws IOException {
overTen.bind(counter.greaterThan(10));
overTen.addListener(ob -> System.out.println("Boolean invalid " + counter.getValue()));
counter.addListener(ob -> System.out.println("Counter Invalid: " + counter.getValue()));
Scene scene = new Scene(createContent(), 320, 240);
stage.setScene(scene);
stage.show();
}
private Region createContent() {
Button button = new Button("Click Me!");
button.setOnAction(evt -> {
System.out.println("Button Clicked");
counter.set(counter.getValue() + 1);
});
VBox vBox = new VBox(button);
vBox.setPadding(new Insets(30));
return vBox;
}
}
The output looks like this:
Button Clicked
Boolean invalid 8
Counter Invalid: 8
Button Clicked
Counter Invalid: 9
Button Clicked
Counter Invalid: 10
Button Clicked
Counter Invalid: 11
What’s going on here?
Notice that the InvalidationListener doesn’t have any code that invokes getValue()
on the BooleanProperty
. So the BooleanProperty
is set to “Valid” when the Binding
is established and there’s no other code that will set it to “Valid” in this application. At this point also, the BooleanProperty
has a value of false
.
After the first click on the Button
, the IntegerProperty
is set to “Invalid”, and the BooleanProperty
follows suit, as you would expect. So both InvalidationListeners
are triggered.
This is important: Even though the BooleanProperty is now “Invalid”, if it were to be recalculated it would remain “false”.
Invalidation simply means that it needs to be checked to see what it’s current value is. And if you don’t check it, it remains “Invalid”.
Both of these InvalidationListeners
invoke getValue()
on the IntegerProperty
. So it’s status is set back to “Valid”. But the status of the BooleanProperty
remains “Invalid”, since its getValue()
is never called.
On to click number 2. We see the click, and then we see the InvalidationListener
called for the IntegerProperty
, but not for the BooleanProperty
. This is because the status of the BooleanProperty
was already “Invalid”, so there’s no change, and no triggering of that InvalidationListener
.
A few more clicks, and now the IntegerProperty
is 11, and the BooleanProperty
, if we called its getValue()
would report a new value of true
.
But nothing happens.
This is because the status never was reset to “Valid” by calling getValue()
.
Now, let’s change one line of code, the InvalidationListener
on the BooleanProperty
:
overTen.addListener(ob -> System.out.println("Boolean invalid " + counter.getValue() + " " + overTen.getValue()));
All we’ve done here is to add in a call to getValue()
on the BooleanProperty
. So now every time it’s invalidated, we’re calling getValue()
and resetting it to “Valid”.
The output now looks like this:
Button Clicked
Boolean invalid 8 false
Counter Invalid: 8
Button Clicked
Boolean invalid 9 false
Counter Invalid: 9
Button Clicked
Boolean invalid 10 false
Counter Invalid: 10
Button Clicked
Boolean invalid 11 true
Counter Invalid: 11
Now we see that the BooleanProperty
in invalidated every time the IntegerProperty
is invalidated, and the InvalidationListener
on the BooleanProperty
is triggered.
One last change, to show another aspect you need to be aware of. This time we’re going to remove the getValue()
calls on the IntegerProperty
. So it will never directly be reset to “Valid”:
overTen.addListener(ob -> System.out.println("Boolean invalid " + overTen.getValue()));
counter.addListener(ob -> System.out.println("Counter Invalid: "));
And this is what the output looks like:
Button Clicked
Boolean invalid false
Counter Invalid:
Button Clicked
Boolean invalid false
Counter Invalid:
Button Clicked
Boolean invalid false
Counter Invalid:
Button Clicked
Boolean invalid true
Counter Invalid:
You can see that the InvalidationListener
on the IntegerProperty
is invoked every time the Button
is clicked. This is because, in order to recalculate the value of the BooleanProperty
, the Binding
has to implicitly invoke the getValue()
on the IntegerProperty
. So it actually does get reset each time.
It is possible to write a Binding
that uses an ObservableValue
as a trigger, but doesn’t call getValue()
on that ObservableValue
when it’s own getValue()
is called. This is something you need to watch out for.
Using ChangeListener
We’ve seen how “Invalidation” is different from “Changed”, and how InvalidationListeners
work. But what about ChangeListener
?
ChangeListeners
are invoked whenever the value in the ObservableValue
actually changes. This means that, behind the scenes, the getValue()
method of the ObservableValue
is going to be called. This is probably why getValue()
is defined in ObservableValue - which introduces ChangleListener - and not in Observable: You need getValue()
in order to implement ChangeListener
.
An Example
Let’s go back to that same example, but modify it to use ChangeListeners instead:
public class ChangeListenerDemo extends Application {
private final BooleanProperty overTen = new SimpleBooleanProperty(false);
private final IntegerProperty counter = new SimpleIntegerProperty(7);
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) throws IOException {
overTen.bind(counter.greaterThan(10));
overTen.addListener((ob, oldValue, newValue) -> System.out.println("Boolean changed: " + newValue));
counter.addListener((ob, oldValue, newValue) -> System.out.println("Counter changed: " + newValue));
Scene scene = new Scene(createContent(), 320, 240);
stage.setScene(scene);
stage.show();
}
private Region createContent() {
Button button = new Button("Click Me!");
button.setOnAction(evt -> {
System.out.println("Button Clicked");
counter.set(counter.getValue() + 1);
});
VBox vBox = new VBox(button);
vBox.setPadding(new Insets(30));
return vBox;
}
}
Just two lines have been changed. You can see that ChangeListener
gets both the old value and the new value of the Property
as parameters. We’re just going to look at the new value here.
The output looks like this:
Button Clicked
Counter changed: 8
Button Clicked
Counter changed: 9
Button Clicked
Counter changed: 10
Button Clicked
Boolean changed: true
Counter changed: 11
This is quite different. The ChangeListener
for the IntegerProperty
is called every time, but the ChangeListener
for the Boolean
is only called once, when the value changes from false
to true
.
How Do They Interact?
Let’s see. Back to the same example, but we’ll put both an InvalidationListener
and a ChangeListener
on each Property
. The InvalidationListeners
are just going to output that they were triggered, without calling getValue()
.
public class ChangeListenerDemo extends Application {
private final BooleanProperty overTen = new SimpleBooleanProperty(false);
private final IntegerProperty counter = new SimpleIntegerProperty(7);
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) throws IOException {
overTen.bind(counter.greaterThan(10));
overTen.addListener((ob, oldValue, newValue) -> System.out.println("Boolean changed: " + newValue));
counter.addListener((ob, oldValue, newValue) -> System.out.println("Counter changed: " + newValue));
overTen.addListener(ob -> System.out.println("Boolean invalid "));
counter.addListener(ob -> System.out.println("Counter invalid "));
Scene scene = new Scene(createContent(), 320, 240);
stage.setScene(scene);
stage.show();
}
private Region createContent() {
Button button = new Button("Click Me!");
button.setOnAction(evt -> {
System.out.println("Button Clicked");
counter.set(counter.getValue() + 1);
});
VBox vBox = new VBox(button);
vBox.setPadding(new Insets(30));
return vBox;
}
}
The output looks like this:
Button Clicked
Boolean invalid
Counter invalid
Counter changed: 8
Button Clicked
Boolean invalid
Counter invalid
Counter changed: 9
Button Clicked
Boolean invalid
Counter invalid
Counter changed: 10
Button Clicked
Boolean invalid
Boolean changed: true
Counter invalid
Counter changed: 11
Okay, so both of the InvalidationListeners
are invoked every time the value in the IntegerProperty
changes. This means that both Properties
are reset to “Valid” by their ChangeListener
even if the ChangeListener
isn’t triggered on that Property
.
Yes, But Which Should You Use?
The truth is, for virtually every use case where you call the ObservableValue
’s getValue()
method, there is no practical difference between ChangeListener
and InvalidationListener
. So use whichever you feel more comfortable with.
I Prefer InvalidationListener
Personally, I find that InvalidationListener
is a little bit simpler to implement, because there’s only one parameter and calling ob.getValue()
is no more difficult than accessing the newVal
parameter of ChangeListener
.
I find that InvalidationListener
requires you to think a little bit more about the underlying mechanism that triggers the listener, and this is a good thing. You should understand what’s really going on and base your design on that understanding. Finally, you should be aware the Bindings
are based on InvalidationListeners
under-the-hood, and if your Bindings
are doing strange things, being comfortable with the concepts behind InvalidationListeners
is going to make it easier to fix the problems.
Sometimes the Invalidation is More Important Than the Change
There are occasions where having a Property
become “Invalid” may be more important than seeing the value change. Perhaps there are elements of you application that aren’t represented by Properties
that need to be re-evaluated at certain times. In that case, an InvalidationListener
might be more appropriate than a ChangeListener
.
And remember, it’s very possible that an InvalidationListener
might trigger even when a ChangeListener
wouldn’t.
Use ChangeListener If You Care About the Old Value
Since ChangeListener
gives you both the old and the new value of the property, if your logic requires that you do something different based on different values the Property might changed from, then ChangeListener
is the way to go. Another way of saying this is: Use ChangeListener if you care about the nature of the change.