IntelliJ IDEA
IntelliJ IDEA – the Leading Java and Kotlin IDE, by JetBrains
Tutorial: Reactive Spring Boot Part 4 – A JavaFX Line Chart
In this step we see how to create a JavaFX application that shows a line chart. This application uses Spring for features like inversion of control.
This is the fourth part in our tutorial showing how to build a Reactive application using Spring Boot, Kotlin, Java and JavaFX. The original inspiration was a 70 minute live demo.
This blog post contains a video showing the process step-by-step and a textual walk-through (adapted from the transcript of the video) for those who prefer a written format.
This tutorial is a series of steps during which we will build a full Spring Boot application featuring a Kotlin back end, a Java client and a JavaFX user interface.
This step continues our integration of JavaFX and Spring, and at the end of it we’ll be able to show an empty line chart. We’ll populate it with data later in the tutorial.
Creating the Scene
- Open the stock-client project that we created back in step 2, and go back to the stock-ui module that we created in step 3.
- Open StageInitializer, we’re going to update this to display the user interface for the application.
- In onApplicationEvent, call stage.setScene() and give it a parent (currently undefined) and a width and height, 800 by 600.
public void onApplicationEvent(StageReadyEvent event) { Stage stage = event.getStage(); stage.setScene(new Scene(parent, 800, 600)); }
(note: this code will not compile yet)
- Create parent as a local variable of type Parent.
- (Tip: If you press Alt+Enter on the red parent text and choose “Create local variable” IntelliJ IDEA creates the variable and works out this needs to be of type Parent.)
- Call stage.show() after initialising the stage.
public void onApplicationEvent(StageReadyEvent event) { Parent parent; Stage stage = event.getStage(); stage.setScene(new Scene(parent, 800, 600)); stage.show(); }
Now we need to work out where this parent is going to come from.
Using FXML
We’re going to use FXML to define which elements are on the user interface. Declaring the view elements in FXML gives a nice clean separation between the view and the model and controller if we’re following an MVC pattern.
- Declare an FXMLLoader local variable in the onApplicationEvent method.
public void onApplicationEvent(StageReadyEvent event) { FXMLLoader fxmlLoader; Parent parent; // ...rest of the method }
We haven’t declared a dependency on FXML classes yet, so let’s add a Maven dependency.
- In our pom.xml file, we need to add a dependency on javafx-fxml.
- (Tip: pressing Alt+Enter on the red FXMLLoader text in the editor gives the option to “Add Maven Dependency“.)
- Add org.openjfx:javafx-fxml as a dependency, version 13.
<dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> <version>13</version> </dependency>
- Now import the FXMLLoader class in StageInitializer.
- (Tip: pressing Alt+Enter on the red FXMLLoader text in the editor gives the option to “Import class“, or IntelliJ IDEA can do this without prompting if auto-import is set up this way.)
- Create a chartResource field in the StageInitializer, it’s going to be a Spring Resource.
- We can use the @Value annotation to tell Spring where to find the file, let’s say it’s on the classpath and it’s a file called chart.fxml.
- In the FXMLLoader constructor, pass in chartResource.getURL().
- This getURL throws an Exception, so surround the call with a try/catch block. In the catch section we’ll throw a new RuntimeException for the purposes of keeping the tutorial simple, but this is not a useful way to deal with Exceptions in production code.
- Now we can finally initialise our parent, by calling fxmlLoader.load().
import javafx.fxml.FXMLLoader; import javafx.scene.*; import javafx.stage.Stage; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationListener; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class StageInitializer implements ApplicationListener<StageReadyEvent> { @Value("classpath:/chart.fxml") private Resource chartResource; @Override public void onApplicationEvent(StageReadyEvent event) { try { FXMLLoader fxmlLoader = new FXMLLoader(chartResource.getURL()); Parent parent = fxmlLoader.load(); Stage stage = event.getStage(); stage.setScene(new Scene(parent, 800, 600)); stage.show(); } catch (IOException e) { throw new RuntimeException(e); } } }
Creating the FXML file
- Go to the resources directory (src/main/resources) and create a new FXML file, chart.fxml.
- (Tip: If you create a “new FXML file” using IntelliJ IDEA, you’ll get a basic FXML file created for you.)
- The top level element should be a VBox, if the file was created by IntelliJ IDEA we need to change it from AnchorPane to VBox.
- Make sure the VBox has an fx:controller property with a value of ChartController.
- (Tip: IntelliJ IDEA has code generation even inside the FXML file, so we can create this missing controller class by pressing Alt+Enter on the red ChartController text and selecting “Create class”.)
- Create the ChartController in the same package as all the other classes. Make sure the path to the controller in fx:controller contains the full package name.
- We can use Optimize Imports to remove all the unnecessary imports from the FXML file.
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="ChartController"> </VBox>
Start this application, by going back to the SpringBootApplication class and running it using Ctrl+Shift+F10 for windows or Ctrl+Shift+R for macOS.
A Java window should pop up with the dimensions we set in the Stage. There’s nothing in there yet as we haven’t put anything into the view.
Setting the application title
We’re going to make a small change to see that we can control what is displayed in the window.
- Go back to StageInitializer, and add a line to set the title of the view to be applicationTitle.
- Create a String field for applicationTitle.
- (Tip: we can get IntelliJ IDEA to create a constructor with the appropriate parameters to initialise this field.)
- Adding a constructor parameter to populate this field means we can use Spring to populate the value of this title.
- Use the @Value annotation to set a default value for this parameter.
public class StageInitializer implements ApplicationListener<StageReadyEvent> { @Value("classpath:/chart.fxml") private Resource chartResource; private String applicationTitle; public StageInitializer(@Value("Demo title") String applicationTitle) { this.applicationTitle = applicationTitle; } public void onApplicationEvent(StageReadyEvent event) { try { FXMLLoader fxmlLoader = new FXMLLoader(chartResource.getURL()); Parent parent = fxmlLoader.load(); Stage stage = event.getStage(); stage.setScene(new Scene(parent, 800, 600)); stage.setTitle(applicationTitle); stage.show(); } catch (IOException e) { throw new RuntimeException(); } } }
When we re-run the application, we should see JavaFX uses this new title in the title bar
Setting an application title from application.properties
Hard-coding string values is not good practice for a number of reasons, so let’s get this title from somewhere else.
- In src/main/resources/application.properties, add a new property called spring.application.ui.title, and set a value for the title.
spring.application.ui.title=Stock Prices
- In StageInitializer, we can use SpEL to say that we want to use this property for the title of our application. Change the @Value of application title to point to this property
public StageInitializer(@Value("${spring.application.ui.title}") String applicationTitle) { this.applicationTitle = applicationTitle; }
Now when we run the application (for example by pressing Ctrl twice to Run Anything), we should see this value from application properties used as the title of the window.
Get JavaFX controllers from Spring
There’s one last thing we need to do to make the most of Spring in this JavaFX application, and that’s to be able to use the beans from the application context in the JavaFX wiring.
- Call setControllerFactory on our fxmlLoader. We need to give this a lambda expression that, given a class, returns an Object. This sounds like a job for the Spring application context.
- Create an ApplicationContext field called applicationContext.
- Add a new constructor parameter for this field so that is initialised.
- (Tip: if you press Alt+Enter on a field that has not been initialised, IntelliJ IDEA will offer the option to add a constructor parameter.)
- Now we can call getBean on applicationContext within the setControllerFactory lambda parameter to provide the controllers that JavaFX needs.
// ...start of class happens above this line private ApplicationContext applicationContext; public StageInitializer(@Value("${spring.application.ui.title}") String applicationTitle, ApplicationContext applicationContext) { this.applicationTitle = applicationTitle; this.applicationContext = applicationContext; } @Override public void onApplicationEvent(StageReadyEvent event) { try { FXMLLoader fxmlLoader = new FXMLLoader(chartResource.getURL()); fxmlLoader.setControllerFactory(aClass -> applicationContext.getBean(aClass)); // ...rest of the class
Creating a line chart
All our wiring is complete, so let’s finally create our line chart.
- In chart.fxml, inside the VBox, we’ll declare we want a LineChart.
- We need to add, as a sub element, an xAxis.
- Inside xAxis, add a CategoryAxis, this means the x-axis is going to have strings as values. This is our Time axis so add a label of “Time”.
- At the same level as xAxis, add a yAxis.
- Inside the yAxis, declare a NumberAxis. This axis is for the stock price, so add a label of “Price”.
- Set the height of the chart to 600 by setting the prefHeight property on LineChart to 600.
- We need to give the LineChart an fx:id, which is the ID of the field in the ChartController that will contain the reference to this LineChart. Let’s call it “chart”.
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.chart.LineChart?> <?import javafx.scene.chart.CategoryAxis?> <?import javafx.scene.chart.NumberAxis?> <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.mechanitis.demo.stockui.ChartController"> <LineChart prefHeight="600" fx:id="chart"> <xAxis> <CategoryAxis label="Time"/> </xAxis> <yAxis> <NumberAxis label="Price"/> </yAxis> </LineChart> </VBox>
- (Tip: IntelliJ IDEA can identify there’s no field called chart in the controller class. Press Alt+Enter on the “chart” value of fx:id and select “Create field ‘chart'”, and the IDE will generate the correct field on ChartController.)
- ChartController should have a LineChart field called chart. We said the chart has a String x axis and a Number y axis, so we can declare this as a LineChart<String, Double>
- We can add the FXML annotation to show this field is populated from an FXML file.
- Make sure ChartController is annotated as a Spring @Component.
import javafx.fxml.FXML; import javafx.scene.chart.LineChart; import org.springframework.stereotype.Component; @Component public class ChartController { @FXML public LineChart<String, Double> chart; }
Now when we run the application, we should see the outline of a line chart shown in our window, with numbers for our price y axis, and time on the x axis.
We have successfully created a JavaFX application that is integrated into Spring Boot, that uses FXML to declare what should be in the view. In the following videos of this tutorial, we’ll get this chart updating itself with stock prices in real time.
Full code is available on GitHub. You can learn more about the features for working with Spring in IntelliJ IDEA here.