By Adam Bien

Combining Convention over Configuration and Dependency Injection with Scene Builder

In theory, you could draw the entire UI for an application using a nice tool without any programming skills and doing so would be faster than any code-driven development. JavaFX comes with a nice design tool called Scene Builder out of the box. This article explains how to combine “drawings” and serious coding to become even more productive.

Adam Bien shows you how to create composite JavaFX views.

The WYSIWYG Revolution

Java user interface toolkits have always been accompanied by WYSIWYG visual editors. Rapid development and easy maintainability were the main drivers behind the visual development trend. However, because most of the visual editors automatically generated code from visual representations, the code was hard to maintain. The only interface between the developer and the visual editor were blank spots in the generated code, so-called protected regions, where any hooks to the presentation logic had to be placed. Also, the productivity achieved with the WYSIWYG editors was traditionally indirectly proportional to the complexity of the UI; the more challenging the UI, the harder it becomes to fully rely on the code-free approach.

Surprisingly the JavaFX Scene Builder does not incur the challenges described above. Instead of generating layout code, Scene Builder creates a configuration for an FXMLLoader. The javafx.fxml.FXMLLoader is a factory that encapsulates the creation of the UI and returns the root javafx.scene.Parent. At any time, you can equally well code the node hierarchy manually and return the Parent to the application.

With JavaFX Scene Builder, instead of relying on blank spots created by a tool, the developer exposes presenter methods and fields to the Scene Builder using annotations or conventions. These exposed hooks are glued together by the FXMLLoader at load time using the generated FXML configuration. A presenter declared in the FXML file is instantiated by the FXMLLoader, which enables the interaction with the exposed hooks.

UI elements are instantiated with the information stored in the FXML file and injected into the presenter. Any public method or any method with the @FXML annotation can be used as an event listener hook. The creation of the presenter and the dependency management of the generated widget is a classic example of Inversion of Control: instead of being dependent on the generated code, the generated code becomes dependent on the handcrafted presenter. Even more appropriate is the Hollywood principle: “Don’t call us, we’ll call you.” Let the FXML runtime handle the widget injection and invocation of the event handlers for you.

The JavaFX WYSIWYG approach is less brittle, more productive, highly maintainable and, therefore, particularly interesting for building large enterprise applications.

A Bigger Question

Even an ambitious single-page application might contain multiple subviews organized by a main view that dictates the structure. Also, a single view might be too complex for a single controller. Because the FXML file produces a single node (view) and instantiates a single controller, the complexity might grow quickly and make future extensions or bug fixing unnecessarily difficult. Multiple controllers per FXML view would simplify the codebase. In fact, I posted the following question to the official JavaFX JIRA bug tracker site on November 16, 2011:

“Currently only a single controller per FXML file is supported. This restriction is problematic:1. Bigger views could cause the introduction of “God Controllers” with several dozen of methods.2. Several controllers per view (=FXML) file are more compatible with the ‘Separation of Concerns’ idea.”

The answer from the JavaFX engineers was clear:

“Each UI component should be able to use a dedicated controller. The 1:1 restriction between FXML-file and controller should be a convention, not a restriction.The 1:1 association between an FXML document and its controller is by design. Have you considered using includes to separate your markup into multiple documents? Each document can have its own controller.”

An Idea Was Born: Use FXML

The conventions introduced in FXML could be easily applied to the whole structure of the application. A screen is going to be represented by a Java package named after the view. Each package is always going to have the same contents:

  • [package_name]View.java: A class responsible for automatically loading the FXML file. The name of the FXML file is directly derived from the class name of this view.
  • [package_name].fxml: A file that is a product of the design process. Usually this file is created with Scene Builder, but it could be refined in the IDE. The presenter is instantiated by the FXMLLoader, but it needs to be declared manually in the FXML file for this purpose.
  • [package_name]Presenter.java: Contains UI elements injected by the FXML runtime and methods used as action listeners. Also services and models are injected into the presenter as singletons.
  • [package_name]Service.java: A Plain Old Java Object (POJO) responsible for the communication with a service or the implementation of any kind of logic.
  • [package_name]Model.java: A POJO used for sharing the state between views or the implementation of business logic.
  • [package_name].css: An optional Cascading Style Sheet (CSS) automatically loaded by the view.
  • [package_name].properties: An optional resource bundle loaded and passed to thejavafx.fxml.Initializable#initialize method as a parameter.
  • configuration.properties: An optional configuration file containing properties injected into the presenter.

Convention paired with Dependency Injection successfully eliminates the need for configuration and, therefore, duplication. Convention also enforces consistent code structure and naming, which in turn significantly improves the maintainability. Adopt a “don’t make me think” approach for the obvious stuff, and don’t waste your time repeatedly rethinking the code organization and naming. All the elements should be immediately recognized with a single glance.

Say Hello to afterburner.fx: An Extracted Library

My initial idea was verified through a real-world project: managing attendees for my airhacks Java workshops at the Munich Airport. Productivity increased and the codebase was reduced at the same time. All views were designed with Scene Builder without any coding involved. A final refactoring led to a standalone microframework that began with two classes, whose name was inspired by the airport and which was extracted at the end from the actual application.

afterburner.fx implements the conventions mentioned in the previous section and adds JSR 330 @Inject Dependency Injection for presenters, models, and views. Also, the loading of the FXML, CSS, and property files; the instantiation of the presenter; and the injection of models and services come with the inheritance from com.airhacks.afterburner.views.FXMLView. All you need to do is stick to the conventions and instantiate a view.

The following command will generate a sample application for you. It is also the simplest possible way to get started with afterburner.fx.

mvn archetype:generate -Dfilter=com.airhacks:igniter

Let’s create an entirely new view, “hello,” and, therefore, a Java package with the same name. According to the conventions, the contents of the package are already predefined: HelloView.java, HelloPresenter.java, HelloService.java, and hello.fxml.

Note: The source code for the example described in this article can be downloaded here.

The HelloService class is optional, but it would usually be used in a realistic application. A service does not have to conform to the naming conventions and is usually named after its responsibilities. In our example, the service’s responsibility is to say “hello”—therefore, the name suites it perfectly.

The view needs to extend the com.airhacks.afterburner.views.FXMLView—the place where the conventions happen:

public class HelloView extends FXMLView {
}

One of the most-asked questions is, “Why is this class empty?” A variation of that question is, “What do I usually have to put into the view class?” A concrete FXMLView is usually empty, but you could enhance the Parent node from the FXML file by overriding the methodgetView():

   @Override
    public Parent getView() {
        return super.getView();
    }

However, manual view construction is usually not necessary and the view remains empty in the vast majority of cases. Subclasses ofFXMLView represent the whole FXML file and are not a JavaFX component. Only the invocation of getView enforces the eventual construction of the javafx.scene.Parent instance.

Less important, but useful in more-complex scenarios, is the method getPresenter(). As the name of the method implies, it returns the presenter that is associated with the view and is created by the FXML runtime. A presenter is a POJO on steroids and supports injection of UI components designed with Scene Builder, as well as the injection of services, models, or any POJOs equipped with a default constructor, as shown in Listing 1:

public class HelloPresenter {
    @Inject
    HelloService hello;
    @FXML
    TextField nameTextField;

    public void sayHello() {
        hello.sayHelloTo(nameTextField.getText());
    }
}

Listing 1. Example of the injection of UI components and services

Fields marked with the @FXML annotation are injected by the JavaFX runtime and @Inject is a feature of the afterburner.fx framework. In addition, all the public methods of the presenter are usable as event listeners.

Any POJO equipped with a parameterless constructor can be directly injected into the presenter. The injection starts in the presenter and works in all injected classes. In addition to injection, the @PostConstruct and @PreDestroy annotations are supported and the methods are invoked accordingly. (see Listing 2).

public class HelloService {
    @PostConstruct
    public void init() {
        System.out.println("Initializing");
    }

    public void sayHelloTo(String name) {
        System.out.println("Hello " + name);
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("Cleaning up!");
    }
}

Listing 2. Lifecycle hooks with @PreDestroy and @PostConstruct annotations

A JavaFX application is started by executing the launch method and the UI construction is performed in the method start with the parameter javafx.stage.Stage. The initialization of an afterburner.fx application is the same, but requires significantly less code (see Listing 3).

public class App extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        HelloView appView = new HelloView();
        Scene scene = new Scene(appView.getView());
        stage.setScene(scene);
        stage.show();
    }

    @Override
    public void stop() throws Exception {
        Injector.forgetAll();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Listing 3. Starting an afterburner.fx application

Composite Views

Tab, SplitPane, or Accordion widgets act as a skeleton for multiple views. Introducing a composite view has only a little impact on the source code: Instead of directly instantiating HelloView in the main method, the composite view is instantiated first:

        AppView appView = new AppView();
   Scene scene = new Scene(appView.getView());

Also a composite view is designed with Scene Builder, as shown in Figure 1.

javafx-productivity-f1

Figure 1. Composite view designed with Scene Builder

All composite components comprise multiple AnchorPane instances, which are again eligible for injection into the presenter, as shown in Listing 4:

public class AppPresenter implements Initializable {
    @FXML
    AnchorPane north;
    @FXML
    AnchorPane south;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        HelloView northView = new HelloView();
        north.getChildren().add(northView.getView());

        HelloView southView = new HelloView();
        south.getChildren().add(southView.getView());
    }
}

Listing 4. Injecting subviews designed in Scene Builder

The presenter does not care what kind of container element was used to structure the application. The placeholder elements areAnchorPane instances that can be populated with any manually instantiated views. Static views can be eagerly wired in the initializemethod. Equally well, a view can be dynamically created or replaced lazily at any point in time.

Convenient Build Process

Maven 3 is the most popular build system in enterprise projects. Unfortunately Maven’s own Convention over Configuration behavior is not fully compatible with the conventions of afterburner.fx. Loading the FXML, CSS, and property files from src/main/java requires some tweaks in the pom.xml file (see Listing 5):

<build>
...
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.fxml</include>
                    <include>**/*.css</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
...
</build>

Listing 5. Example of changes that need to be made in the pom.xml file

An optional, but convenient, approach for desktop applications is to configure the Maven dependency plugin (see Listing 6).

<plugin>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <id>unpack-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>unpack-dependencies</goal>
                        </goals>
                        <configuration>
                            <excludeScope>system</excludeScope>
                            <excludeGroupIds>junit,
                                  org.mockito,
                                  org.hamcrest
                            </excludeGroupIds>
                                 <outputDirectory>${project.build.directory}/classes</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
</plugin>

Listing 6. Extracting all dependencies into a single location for further packaging

After the declaration of maven-dependency-plugin, all dependencies declared in the pom.xml file are merged into a single JAR file and can be passed along with the application. The JDK tool javafxpackager creates the JAR executable from the command line via thejava -jar hello-app.jar command. Because javafxpackager is not a Maven plugin, it is integrated into the build with exec-maven-plugin, as shown in Listing 7:

<plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <id>package-jar</id>
                        <phase>package</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <configuration>
                         <executable>${env.JAVA_HOME}/bin/javafxpackager</executable>
                            <arguments>
                                <argument>-createjar</argument>
                                <argument>-nocss2bin</argument>
                                <argument>-appclass</argument>
                                <argument>com.airhacks.followme.App</argument>
                                <argument>-srcdir</argument>
                                <argument>${project.build.directory}/classes</argument>
                                <argument>-outdir</argument>
                                <argument>.</argument>
                                <argument>-outfile</argument>
                                <argument>${project.artifactId}-app</argument>
                                <argument>-v</argument>

            </arguments>
                        </configuration>
                    </execution>
                </executions>
</plugin>

Listing 7. Integrating javafxpackager with Maven

With both plugins installed and tweaks made to the resource management, simply executing mvn clean install will create an executable JAR file in the target folder.

More-Ambitious Features

More-sophisticated applications can contain many dynamically loaded views. Loading views for the entire application at initialization time is waste of the users’ time. afterburner.fx can use its own thread pool to load views asynchronously with the FXMLView#getViewAsyncmethod. Instead of waiting for the return value, the calling presenter may provide a callback (Consumer<Parent>) and continue with the initialization, as shown in Listing 8:

public class DashboardPresenter implements Initializable {
//...
    @FXML
    Pane lightsBox;

    public void createLights() {
        for (int i = 0; i < 255; i++) {
            final int red = i;
            LightView view = new LightView((f) -> red);
            view.getViewAsync(lightsBox.getChildren()::add);
        }
    }
//...
}
<pre>

Listing 8. Asynchronous view instantiation

Services might require additional information that is needed to connect to an external service. afterburner.fx injects properties from an ordinary property file into presenters or injected POJOs. Imagine that the greeting message in our HelloService needs to be configurable. For this purpose, we just need to denote a String field with the @Inject annotation, as shown in Listing 9:

import javax.inject.Inject;
public class HelloService {
    @Inject
    private String message;
    //...
    public void sayHelloTo(String name) {
        System.out.println(this.message + " " + name);
    }
}

Listing 9. Application configuration with String injection

According to the already introduced convention, a configuration.properties file is going to be automatically loaded from the view package and the contents become illegible for injection. Also, the injection is conventional—the name of the field has to match a key in the property file:

message=Hey

After the initialization of HelloService, the field message is set to “Hey.”

Property files are well suited for static configuration. In more-dynamic cases, any function instance such asjava.util.function.Function<Object, Object> can also be used as a configuration source. Thankfully, an ordinaryMap<Object,Object> or any other method resolving a key into an object can be easily converted into such function, as shown in Listing 10:

public class App extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        <strong>Map<Object, Object> customProperties = new HashMap<>();</strong>
        <strong>customProperties.put("message", " Programmatic hello");</strong>
        <strong>Injector.setConfigurationSource(customProperties::get);</strong>

        AppView appView = new AppView();
        Scene scene = new Scene(appView.getView());
        stage.setScene(scene);
        stage.show();
    }

Listing 10. Converting Map<Object,Object> into a configuration source

Sometimes it might be necessary to override the configuration at application start for testing or diagnostic purposes. Consequently, a system property in afterburner.fx has the highest possible priority: It overrides any programmatically provided property or any property defined in configuration.properties. See Listing 11.

public class App extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        Map<Object, Object> customProperties = new HashMap<>();
        customProperties.put("message", " Programmatic hello");
        Injector.setConfigurationSource(customProperties::get);

        AppView appView = new AppView();
        Scene scene = new Scene(appView.getView());
        stage.setScene(scene);
        stage.show();
    }

Listing 10. Converting Map<Object,Object> into a configuration source

Sometimes it might be necessary to override the configuration at application start for testing or diagnostic purposes. Consequently, a system property in afterburner.fx has the highest possible priority: It overrides any programmatically provided property or any property defined in configuration.properties. See Listing 11.

public class App extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        System.setProperty("message", " Hello from system");
        AppView appView = new AppView();
        Scene scene = new Scene(appView.getView());
        stage.setScene(scene);
        stage.show();
    }
//...
}
<pre>

Listing 11. System properties have the highest priority

Conclusion

afterburner.fx started as a proof of concept and a single class without any external dependencies. It was surprisingly successful from its inception. Most of the “advanced” features described in this article were requested by projects using afterburner.fx in production. I tried to reject as many requests as possible and always requested a use case to verify the urgency and real-world applicability. My approach was “keep it simple, keep it stupid.”

You could easily build your own Model View Presenter (MVP) framework for JavaFX in a few hours. The idea is more powerful than the tool; Convention over Configuration combined with Dependency Injection helps you to build JavaFX applications quickly and without any boilerplate code. Inversion of Control within the WYSIWYG editor comes with additional powers and scales well from quick prototyping to creating hundreds of views. Your business code is not dependent on the generated FXML file—the reverse is true.

JavaFX comes with Java SE 8 out of the box and is Java. It doesn’t have any external dependencies, so your code remains dependent only on JDK 8 itself. Using the same language on the client and server makes you even more productive. And you can use the same integrated development environment (IDE), debugging, and profiling tools for the realization of both the presentation and the business logic.

About the Author

Consultant and author Adam Bien is an Expert Group member for the Java EE 6, Java SE 7, EJB 3.X, JAX-RS, and JPA 2.X JSRs. He has worked with Java technology since JDK 1.0 and with Servlets/EJB 1.0, and he is now an architect and developer for Java SE and Java EE projects. He has edited several books about JavaFX, J2EE, and Java EE, and he is the author of Real World Java EE Patterns—Rethinking Best Practices and Real World Java EE Night Hacks. Adam is also a Java Champion, Top Java Ambassador 2012, and a JavaOne 2009, 2011, 2012, and 2013 Rock Star. Adam occasionally organizes Java EE workshops at Munich Airport.

Screen Shot 2014-11-11 at 10.24.48

 

 

 

 

This article “Structuring Complex JavaFX 8 Applications for Productivity” by Adam Bien was originally published on Oracle Technology Network in October 2014 – http://www.oracle.com/technetwork/articles/java/javafx-productivity-2345000.html

Structuring Complex JavaFX 8 Applications for Productivity

| Java Language| 4,064 views | 2 Comments
About The Author
-

2 Comments

  • TJK
    Reply

    Adam,

    This is a fantastic article. Thanks very much.
    I was wondering which approach to take to design a complex UI where widgets are displayed based on permissions.
    Should we go multiple FXMLs based on role – or hide the components at runtime based on permission.
    Any design suggestions?

    Thanks

  • Alex
    Reply

    Hi Adam, thanks a lot for sharing this framework with the community. Just to provide feedback on your documentation, I find it difficult to understand because you don’t go into too many specifics of what the afterburner classes actually do. I have to look into the source code to see what it’s doing to understand the concepts you’re talking about. I would find it much easier to grasp if you provided a list that specifically describes what your classes do, and what this is saving for us as opposed to not using afterburner. I know it’s my fault for not being an expert developer, your documentation seems to be aimed at experts, but I’m guessing many of them have created or use such frameworks already.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>