Tutorial: Reactive Spring Boot, Part 6: Displaying Reactive Data
Learn more about displaying reactive data within your reactive Spring Boot apps.
Join the DZone community and get the full member experience.
Join For FreeThis 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.
Here is everything you've missed so far:
Tutorial: Reactive Spring Boot, Part 2: A REST Client for Reactive Streams
Tutorial: Reactive Spring Boot, Part 3: A JavaFX Spring Boot Application
Tutorial: Reactive Spring Boot, Part 4: A JavaFX Line Chart
Tutorial: Reactive Spring Boot, Part 5: Auto-Configuration for Shared Beans
In this lesson, we look at connecting our JavaFX chart to our Kotlin Spring Boot service to display real-time prices. 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 part four, we created a JavaFX Spring Boot application that shows an empty line chart. In the last installment (part five), we wired in a WebClientStockClient to connect to the price service. In this installment, 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 aninitialize
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 consumeStockPrice
. - 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 to 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 the 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 into 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 these 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 behavior 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.
The full code is available on GitHub.
Further Reading
Streaming Live Updates From a Reactive Spring Data Repository
Published at DZone with permission of Trisha Gee, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments