Tutorial: Reactive Spring Boot, Part 4: A JavaFX Line Chart
Learn how to create a JavaFX application that shows a line chart.
Join the DZone community and get the full member experience.
Join For FreeThis is the fourth part of 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. If you would like to see previous installments, please check out the Further Reading section below.
In this step, we see how to create a JavaFX application that shows a line chart. This application uses Spring for important features like Inversion of Control.
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
, callstage.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 initializing 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.
1. 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 theStageInitializer
, 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 newRuntimeException
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 beapplicationTitle
. - Create a String field for
applicationTitle
. - (Tip: We can get IntelliJ IDEA to create a constructor with the appropriate parameters to initialize 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 are 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 the 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 initialized.
- (Tip: If you press Alt+Enter on a field that has not been initialized, IntelliJ IDEA will offer the option to add a constructor parameter.)
- Now, we can call
getBean
onapplicationContext
within thesetControllerFactory
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 theVBox
, 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 ayAxis
. - 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 theChartController
that will contain the reference to thisLineChart
. 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 aLineChart
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.
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, which uses FXML to declare what should be in the view.
The full code is available on GitHub.
Further Reading
Tutorial: Reactive Spring Boot, Part 2: A REST Client for Reactive Streams
Tutorial: Reactive Spring Boot, Part 3: A JavaFX Spring Boot Application
Published at DZone with permission of Trisha Gee, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments