IntelliJ IDEA
IntelliJ IDEA – the Leading Java and Kotlin IDE, by JetBrains
Tutorial: Reactive Spring Boot Part 6 – Displaying Reactive Data
In this lesson we look at connecting our JavaFX chart to our Kotlin Spring Boot service to display real time prices.
This is the sixth 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.
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.
In step four, we created a JavaFX Spring Boot application that shows an empty line chart. In the last step (step five), we wired in a WebClientStockClient to connect to the prices service. In this step we’re going get the line chart to show the prices coming from our Kotlin Spring Boot service in real time.
Setting up the chart data
- In ChartController from the stock-ui module, create an
initialize
method, which is called once any FXML fields have been populated. This initialize method will set up where the chart data is going to come from. - Call setData on the chart and create a local variable called data for the List of Series that needs to be passed into this method.
- Create a Series to add to the data list and pass in
seriesData
. - Create
seriesData
as a field which is an empty list, using FXCollections.observableArrayList().
public class ChartController { @FXML private LineChart<String, Double> chart; private WebClientStockClient webClientStockClient; private ObservableList<Data<String, Double>> seriesData = FXCollections.observableArrayList(); public ChartController(WebClientStockClient webClientStockClient) { this.webClientStockClient = webClientStockClient; } @FXML public void initialize() { ObservableList<XYChart.Series<String, Double>> data = FXCollections.observableArrayList(); data.add(new XYChart.Series<>(seriesData)); chart.setData(data); } }
Subscribe to the price data
We need to get the data for our chart from somewhere. This is what we added the webClientStockClient for.
- Inside the initialize method call
pricesFor
onwebClientStockClient
and pass in a symbol to get the prices for. For now, we can hard-code this value as “SYMBOL”. - We need to subscribe something to the Flux that is returned from this call. The simplest thing to do here is to call subscribe and pass in
this
. - Make the ChartController implement java.util.function.Consumer, and have it consume StockPrice.
- Implement the accept method from Consumer.
- (Tip: We can get IntelliJ IDEA to implement the methods required to fulfill this interface by pressing Alt+Enter on the red class declaration text and selecting “Implement methods“.)
public class ChartController implements Consumer<StockPrice> { // fields and constructor here... @FXML public void initialize() { ObservableList<XYChart.Series<String, Double>> data = FXCollections.observableArrayList(); data.add(new XYChart.Series<>(seriesData)); chart.setData(data); webClientStockClient.pricesFor("SYMBOL").subscribe(this); } @Override public void accept(StockPrice stockPrice) { } }
Display the price data
We need to decide what do with a StockPrice when we receive one. We need to add the right values from the stock price to the chart’s seriesData.
- Inside
accept
, callseriesData.add
with a new instance of Data. - For the y value of Data, we can get the time from the stock price and get the value of “second”. This is an int, and needs to be a String, so wrap it in a String.valueOf call.
- (Tip: we can use the postfix completion “arg” to automatically wrap an expression in a method call to simplify calling String.valueOf when we’ve already typed the expression)
- For the x value, we use the price value from stock price.
public void accept(StockPrice stockPrice) { seriesData.add(new XYChart.Data<>(String.valueOf(stockPrice.getTime().getSecond()), stockPrice.getPrice())); }
This seems fine as it is, but there’s another thing to consider with this programming model. Changing the series data will be reflected by an update in the user interface, which will be drawn by the UI thread. This accept
method is running on a different thread, listening to events from the back-end service. So we need to tell the UI thread to run this piece of code when it gets a chance.
- Call Platform.runLater, passing in a lambda expression of the code to run on the UI thread.
public void accept(StockPrice stockPrice) { Platform.runLater(() -> seriesData.add(new XYChart.Data<>(String.valueOf(stockPrice.getTime().getSecond()), stockPrice.getPrice())) ); }
Running the chart application
- Make sure the back-end Kotlin Spring Boot service (StockServiceApplication) is running.
- Go back to the UI module and run our JavaFX application, StockUiApplication.
- You should see a JavaFX line chart that updates automatically from the prices (see 3:20 into the video for an example).
Displaying the symbol name
- Extract the hard-coded symbol into a local variable, we want to use it in more than one place.
- (Tip: we can use IntelliJ IDEA’s Extract Variable (documentation) (video overview) to easily do this)
- We can give the series a label by passing the symbol in to the Series constructor.
public void initialize() { String symbol = "SYMBOL"; ObservableList<XYChart.Series<String, Double>> data = FXCollections.observableArrayList(); data.add(new XYChart.Series<>(symbol, seriesData)); chart.setData(data); webClientStockClient.pricesFor(symbol).subscribe(this); }
Now when we re-run the application, we see the symbol’s name on the label for the series.
Code tidy-up
This code is a little bit unwieldy with all the type information, so let’s simplify. We can use Alt+Enter on many of the class names mentioned here to have IntelliJ IDEA suggest making this changes automatically.
- Import Series itself to remove the unnecessary XYChart prefix.
- Add a static import for observableArrayList, so we don’t need to repeat FXCollections everywhere.
- Add an import for valueOf, to make the line that sets the chart data a little shorter
- Import the Data class to remove another repetition of XYChart.
@Component public class ChartController implements Consumer<StockPrice> { @FXML private LineChart<String, Double> chart; private WebClientStockClient webClientStockClient; private ObservableList<Data<String, Double>> seriesData = observableArrayList(); public ChartController(WebClientStockClient webClientStockClient) { this.webClientStockClient = webClientStockClient; } @FXML public void initialize() { String symbol = "SYMBOL"; ObservableList<Series<String, Double>> data = observableArrayList(); data.add(new Series<>(symbol, seriesData)); chart.setData(data); webClientStockClient.pricesFor(symbol).subscribe(this); } @Override public void accept(StockPrice stockPrice) { Platform.runLater(() -> seriesData.add(new Data<>(valueOf(stockPrice.getTime().getSecond()), stockPrice.getPrice())) ); } }
This refactoring shouldn’t have changed the behaviour of the code, so when we re-run we’ll see everything working the same way as before.
Conclusion
With these very few lines of code, we’ve created a JavaFX application that uses Spring’s WebClient to connect to a Spring Boot service, subscribes to the reactive stream of prices and draws the prices on a line chart in real time.
Full code is available on GitHub. Check out other features for Spring in IntelliJ IDEA.