Skip to content
Eugen Kiss edited this page May 19, 2014 · 27 revisions

You are surely aware of Kotlin's very good interoperability with Java. So why not just use pure Kotlin together with JavaFX instead of KotlinFX? The reason to prefer KotlinFX is the increased programming usability it provides. That is, KotlinFX is JavaFX as if it were designed for Kotlin. In the following, KotlinFX's primary four improvements are presented in detail.

UI Builders

UI Builders make it possible to define the layout of your GUI application in a hierarchical manner thereby improving the closeness of mapping between the structure of the code and the structure of the resulting GUI application.

To give an example, let us say we want to specify the layout of the following GUI in code.

The relevant part of a solution in JavaFX would look something akin to:

BorderPane root = new BorderPane();
root.setPrefSize(400, 400);
root.setPadding(new Insets(10));
HBox top = new HBox(10, new Label("Filter prefix: "), prefix);
top.setPadding(new Insets(0, 0, 10, 0));
top.setAlignment(Pos.BASELINE_LEFT);
root.setTop(top);
root.setCenter(entries);
GridPane right = new GridPane();
right.setHgap(10);
right.setVgap(10);
right.setPadding(new Insets(0, 0, 0, 10));
right.addRow(0, new Label("Name: "), name);
right.addRow(1, new Label("Surname: "), surname);
root.setRight(right);
HBox bottom = new HBox(10, create, update, delete);
bottom.setPadding(new Insets(10, 0, 0, 0));
root.setBottom(bottom);

stage.setScene(new Scene(root));
stage.setTitle("CRUD");
stage.show();

Notice the linear structure of the code and the repetition of identifiers. Compare it with the solution in KotlinFX:

Stage(stage, title = "CRUD") {
    scene = Scene {
        root = BorderPane(padding=Insets(10.0)) {
            prefWidth = 400.0
            prefHeight = 400.0
            top = HBox(spacing=10.0, padding=Insets(bottom=10.0)) {
                alignment = Pos.BASELINE_LEFT
                + Label("Filter prefix: ") + prefix
            }
            center = entries
            right = GridPane(padding=Insets(left=10.0)) {
                hgap = 10.0
                vgap = 10.0
                addRow(0, Label("Name: "), name)
                addRow(1, Label("Surname: "), surname)
            }
            bottom = HBox(spacing=10.0, padding=Insets(top=10.0)) {
                + create + update + delete
            }
        }
    }
}.show()

Clearly, the hierarchical structure of the UI Builders maps much better to the resulting layout of the application. All this is type-safe and type-assisted.

UI Builders can already be beneficial even without nesting. Let us say we wanted to create an uneditable text field with a width of 50 and the initial string "0". This is how it would be done in JavaFX:

TextField count = new TextField("0");
count.setEditable(false);
count.setPrefWidth(50);

By contrast, this is what can be achieved in KotlinFX:

val count = TextField("0") { editable = false; prefWidth = 50.0 }

In these cases, UI Builders, so to say, let you use a tailored constructor which was not foreseen by the class designer.

Interestingly, JavaFX used to provide UI Builders as well which were primarily removed for a very specific technical reason.

A note on the relation of FXML, GUI designers and UI Builders:

Now, not for nothing does JavaFX have support for FXML, and therefore GUI designer applications, as specifying layout code in this linear manner as illustrated above is obviously inconvenient. If you are happy with FXML and your GUI designer of choice then you at least have a good alternative for cases where you cannot resort to FXML or a GUI designer for one reason or another. The question is if UI Builders in KotlinFX can make you as productive or more productive than using FXML and thus reduce your tooling requirements and development time. For though GUI designers have advantages they have disadvantages as well. For example, connecting the FXML file with the rest of the application is somewhat rigid and using a GUI designer can be finicky and may prevent the discovery of abstraction possibilities. With UI Builders you retain all the powerful (abstraction) abilities of Kotlin and you do not have context switches between Kotlin code, XML files and GUI designers.

Simply add kotlinfx.builders.* to your import declarations to use UI Builders.

Extension Properties

Extension Properties provide a field-like syntax for getters and setters of JavaFX classes. The advantage is easy to see. Instead of having to write the following to create an uneditable text field with a width of 50 and the initial string "0":

val count = TextField("0") { setEditable(false); setPrefWidth(50.0) }
val text = count.getText()

you can write this:

val count = TextField("0") { editable = false; prefWidth = 50.0 }
val text = count.text

Using Kotlin's extension properties rather than getters and setters leads to terser code without losing clarity and has better role expressiveness since setting a value syntactically differs from getting a value.

In addition, KotlinFX provides abbreviations for JavaFX properties. For instance, instead of writing

val text = textField.textProperty().getValue()

you may write

val text = textField.textp.v

Simply add kotlinfx.properties.* to your import declarations to use Extension Properties and kotlinfx.abbreviations.* for the abbreviations.

KotlinFX Binding Expressions

KotlinFX Binding Expressions provide a more natural syntax for describing bindings between JavaFX properties. For example, let us say we wanted to bind the progress of a progress bar to a double property. In JavaFX the solution would look something as follow:

SimpleDoubleProperty elapsed = new SimpleDoubleProperty(0);
progress.progressProperty().bind(elapsed.divide(slider.valueProperty()));

In KotlinFX you can express the same requirement like so:

val elapsed = SimpleDoubleProperty(0.0)
progress.progressp bind (elapsed / slider.valuep)

Simply add kotlinfx.bindings.* to your import declarations to use KotlinFX Binding Expressions. However, if you want an even better way to express bindings, or rather functional dependencies, you should take a look at the following section.

Kalium

Kalium is a reactive and very convenient way to define functional dependencies in KotlinFX as an alternative to KotlinFX Binding Expressions.

To understand the motivation let's consider the following Flight Booker GUI case study. In Swing all you had were callbacks which are an unsatisfying solution to encode functional dependencies between components due to their inversion of control and verbosity. See for yourself with the solution to the Flight Booker case study in Java7/Swing. It turns out that data-binding is a good idea to solve these kinds of issues. Not for nothing did JavaFX introduce this feature. The solution can be found here. For illustration, here's an excerpt of the relevant part:

returnDate.disableProperty().bind(flightType.valueProperty().isEqualTo("one-way flight"));
startDate.styleProperty().bind(Bindings.createStringBinding(() ->
    isDateString(startDate.getText()) ? "" : "-fx-background-color: lightcoral"
, startDate.textProperty()));
returnDate.styleProperty().bind(Bindings.createStringBinding(() ->
    isDateString(returnDate.getText()) ? "" : "-fx-background-color: lightcoral"
, returnDate.textProperty()));
book.disableProperty().bind(Bindings.createBooleanBinding(() -> {
    if (flightType.getValue().equals("one-way flight")) {
        return !isDateString(startDate.getText());
    } else {
        return !isDateString(startDate.getText()) ||
               !isDateString(returnDate.getText()) ||
               stringToDate(startDate.getText()).compareTo(stringToDate(returnDate.getText())) > 0;
    }
}, flightType.valueProperty(), startDate.textProperty(), returnDate.textProperty()));

This is what we want albeit there's a need for a non-native “sublanguage” for bindings which is relatively verbose. The KotlinFX version without Kalium is essentially the same .

Inspired by Scala.Rx and some very interesting papers Kalium came into existence to improve the way functional dependencies can be expressed in KotlinFX. So with Kalium this is what can be achieved:

returnDate.disable { flightType.value() == "one-way flight" }
startDate.style  { if (startDate.text().isDate)  "" else "-fx-background-color: lightcoral" }
returnDate.style { if (returnDate.text().isDate) "" else "-fx-background-color: lightcoral" }
book.disable {
    when (flightType.value()) {
        "one-way flight" -> !startDate.text().isDate
        else             -> !startDate.text().isDate || !returnDate.text().isDate
                            || startDate.text().asDate.compareTo(returnDate.text().asDate) > 0
    }
}

Notice the terse, clear and native-looking way these functional dependencies are expressed. You don't have to use a sublanguage. The only thing to be aware of is using () for properties that the expression should depend on.

Another example is the Timer case study where the relevant part goes from

val elapsed = SimpleDoubleProperty(0.0)
progress.progressP bind (elapsed / slider.valueP)
elapsed.addListener { (v,o,n) ->
    numericProgress.text = formatElapsed(n!!.toInt()) }
reset.setOnAction { elapsed.v = 0.0 }

to

val elapsed = V(0.0)
progress.progress { elapsed() / slider.value() }
numericProgress.text { formatElapsed(elapsed()) }
reset.setOnAction { elapsed u 0.0 }

Yet another example is the Temperature Converter case study where the relevant part goes from

celsius.textP.bindBidirectional(fahrenheit.textP, object : StringConverter<String>() {
    override fun fromString(c: String?): String? =
        if (isNumeric(c!!)) cToF(c) else fahrenheit.text
    override fun toString(f: String?): String? =
        if (isNumeric(f!!)) fToC(f) else celsius.text
})

to

celsius.text { if (isNumeric(fahrenheit.text())) fToC(fahrenheit.text()) else celsius.text }
fahrenheit.text { if (isNumeric(celsius.text())) cToF(celsius.text()) else fahrenheit.text }

We see that JavaFX has a special case binding construct for bidirectional dataflow whereas in Kalium it just works without special constructs.

Simply add kotlinfx.kalium.* to your import declarations to use Kalium.

Please note that Kalium is still experimental. Mainly, the current implementation is not efficient. However, for expressing functional dependencies like in the examples above Kalium is fast enough so in these cases you should use Kalium as an alternative for binding expressions simply because of Kalium's better abstraction capabilities and usability in terms of notational aspects.

Clone this wiki locally