New release of Advanced-Bindings
javafxLast week I've released a new minor version of my Advanced-Bindings library. In this post I will describe the new features in more detail.
Logic Bindings
In the standard JavaFX Bindings API there are methods to create bindings for the logical OR and AND operations. The drawback of these methods is that they can only be used to combine exactly two observable boolean values.
In the new LogicBindings class there are now methods to create OR and AND bindings with a variable number of arguments.
ObservableBooleanValue a = ...
ObservableBooleanValue b = ...
ObservableBooleanValue c = ...
ObservableBooleanValue d = ...
BooleanBinding and = LogicBindings.and(a,b,c,d);
BooleanBinding or = LogicBindings.or(a,b,c,d);
NumberBindings divideSafe
We all have learned in school that you can't divide by zero. If you still do it, in java you get an
ArithmeticException
. To prevent it you have to check your divisor. You would probably write something like this:
int dividend = ...
int divisor = ...
int result;
if(divisor != 0){
result = dividend / divisor;
} else {
result = 0;
}
If your divisor is zero than the result is zero, too. Of cause this is wrong from the viewpoint of a mathematician but it might be right for your usecase.
With the standard JavaFX Bindings API you can create a binding with a division, too. And again you have to worry about
the ArithmeticException
. If you do something like this you will probably have a bad time:
IntegerProperty a = new SimpleIntegerProperty();
final NumberBinding result = Bindings.divide(10, a);
But no problem! Fortunately we can build IF-ELSE constructions with the Bindings API. You could try this:
IntegerProperty a = new SimpleIntegerProperty();
NumberBinding result = Bindings
.when(a.isEqualTo(0))
.then(0)
.otherwise(Bindings.divide(10, a));
But wait! This still produces an ArithmeticException
. The reason is that Bindings.divide(10,a)
is immediately
evaluated when the binding is created and at this point in time a
has a value of 0
.
To help in such a situation I added a divideSafe method (with some overloaded variants) to the Advanced-Bindings library. Now you can write the code like this:
IntegerProperty a = new SimpleIntegerProperty();
NumberBinding result = NumberBindings.divideSafe(100, a);
a.set(0);
assertThat(result).hasValue(0);
This won't throw an ArithmeticException
even if a
has a value of 0
. In this case a default value of 0
is used.
If you need another default value for your usecase there are overloaded variants of the divideSafe
method which accept
a third param for the default value (as observable).
ObjectBindings map
The third new feature is the
map binding.
It's best described with an example: Think of a domain class Person
:
public class Person{
private String firstname;
// getter and setter
}
We have a UI with a master-detail view. In the master view we have a list of persons and you can select one of these person objects. In the detail view we like to see all properties of the selected person.
In the UI controller we have an ObjectProperty<Person> selectedPerson
. Now we like to have a StringBinding that always
contains the firstname of the currently selected person. If the user selects another person this binding should
automatically update. This can be done with the map
binding:
ObjectProperty<Person> selectedPerson = new SimpleObjectProperty<>();
ObjectBinding<String> firstname = ObjectBindings.map(selectedPerson, person -> {
return person.getFirstname();
});
The map
method takes an observable value as first param and a mapping function as the second param. The return value
of the mapping function is used as the value for the binding. The example above uses a Java 8 lambda to define the
mapping function but this could be written even shorter with a method reference:
ObjectBinding<String> firstname = ObjectBindings.map(selectedPerson, Person::getFirstname);
Null Safety
But there is one more thing with the map
binding: It's null-safe. To show what this means I'm creating a binding with
the standard JavaFX Bindings API that is doing almost the same:
ObjectBinding<String> firstname = Bindings.createObjectBinding(()-> selectedPerson.get().getFirstname(),selectedPerson);
The
createObjectBinding
method takes a function as first param and an observable value as the second. In the example I'm calling get()
to get
the currently selected person object. After this I'm calling the firstname getter to get the value for the binding. It's
a little bit longer but besides of this it looks similar.
But wait! What is happening when no person is selected? When the selectedPerson
property has a value of null
this
binding will produce a NullPointerException
! To prevent this you would have to add a null check in the binding
function.
With the map
binding you don't need to think about a null check. The mapping function will only be applied when the
dependent observable has a value other than null
.
But what value does the created binding have when the dependent observable has a value of null
? By default the binding
will have a value of null
too. But there is an
overloaded version of the map binding method
that takes a third param with a default value that is used in this case. So you can decide on your own what is the best
for your use case.
Conclusion
I hope you like the new features. To get the new version simple use gradle (or maven):
Gradle:
compile 'eu.lestard:advanced-bindings:0.2.0'
Maven:
<dependency>
<groupId>eu.lestard</groupId>
<artifactId>advanced-bindings</artifactId>
<version>0.2.0</version>
</dependency>