New release of Advanced-Bindings

| tagged as

Last 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>