Consuming Futures
Some background jobs may use CompletableFuture to inform the user interface of results and errors. This is covered in the Returning Futures documentation page.
When building the user interface with Vaadin Flow, you can use callbacks and register them with the CompletableFuture to update your user interface.
For example, a method for handling successful completion could look like this:
Source code
Java
private void onJobCompleted(String result) {
Notification.show("Job completed: " + result);
}|
Note
| The examples on this page only work with push enabled (see Server Push). |
A method for handling errors might look like this:
Source code
Java
private void onJobFailed(Throwable error) {
Notification.show("Job failed: " + error.getMessage());
}Note, that the error handler must accept a Throwable, and not an Exception when you’re working with CompletableFuture.
Successful Completion
If a CompletableFuture completes successfully, you can instruct it to perform a specific operation by calling the thenAccept() method on it. This method takes a Consumer as its input. When the CompletableFuture completes, it calls this consumer with the result.
Here is an example of a button click listener that starts a background job, and updates the user interface when it has completed successfully:
Source code
Java
button.addClickListener(clickEvent -> {
var ui = UI.getCurrentOrThrow();
service.startBackgroundJob()
.thenAccept(ui.accessLater(this::onJobCompleted, null)); 1
});-
The
UI.accessLater()method is explained on the Pushing UI Updates documentation page.
|
Tip
|
If completion only updates UI state, thenAccept() can set a signal directly instead of going through UI.accessLater(), and the bound component updates automatically. See Pushing State with Signals.
|
Exceptional Completion
If a CompletableFuture is completed with an exception, you can instruct it to perform a specific operation by calling the exceptionally() method on it. However, this method works in a different way than thenAccept().
The exceptionally() method takes a Function, instead of a Consumer as input. The exception is passed to the function as input. The function output is used as the result of the CompletableFuture that is returned by exceptionally().
Flow has no version of UI.accessLater() that works with Function. However, since you’re probably not interested in returning a result, you can create a helper function that adapts a Consumer to a Function, like this:
Source code
Java
public static <T> Function<T, Void> consumerToFunction(Consumer<T> consumer) {
return input -> {
consumer.accept(input);
return null;
};
}Here is an example of a button click listener that starts a background job, and uses the helper function to update the user interface if an error occurs:
Source code
Java
button.addClickListener(clickEvent -> {
var ui = UI.getCurrentOrThrow();
service.startBackgroundJob()
.thenAccept(ui.accessLater(this::onJobCompleted, null))
.exceptionally(consumerToFunction(ui.accessLater(this::onJobFailed, null)));
});Loading Content After the View Is Shown
When part of a view depends on data that’s slow to load, you don’t have to make the user wait for all of it before the view is shown. Instead, show a placeholder, load the data in a background job, and replace the placeholder when the data is ready. Each part of the view can load independently, so the fastest parts appear first.
Start loading when the component is attached, and cancel the future when the component is detached, for example when the user navigates away before the data has loaded:
Source code
Java
public class SalesSummaryCard extends Div {
private final SalesService salesService;
public SalesSummaryCard(SalesService salesService) {
this.salesService = salesService;
var placeholder = new ProgressBar();
placeholder.setIndeterminate(true);
add(placeholder); 1
}
@Override
protected void onAttach(AttachEvent attachEvent) {
var ui = attachEvent.getUI();
var loading = salesService.loadSalesSummary(); 2
loading.thenAccept(ui.accessLater(this::showSummary, null));
addDetachListener(detachEvent -> {
detachEvent.unregisterListener();
loading.cancel(true); 3
});
}
private void showSummary(SalesSummary summary) {
removeAll();
add(new H3("Sales this month"), new Span(summary.total().toString())); 4
}
}-
The placeholder is shown as soon as the view is rendered.
-
loadSalesSummary()returns aCompletableFutureand runs in a background thread. The view doesn’t wait for it. -
Cancelling the future prevents
showSummary()from running after the component has been detached. The job itself keeps running unless it checks for cancellation; see Cancelling. -
The placeholder is replaced with the real content through a server push.
To show an error message instead of the content when loading fails, add an exceptionally() callback as described in Exceptional Completion. Cancelling the future on detach also completes it exceptionally, so the callback runs as well when the user navigates away before the data has loaded. When exceptionally() is chained after thenAccept(), the callback receives a CompletionException caused by a CancellationException. Ignore it, so that no error message is shown:
Source code
Java
private void onLoadingFailed(Throwable error) {
if (error.getCause() instanceof CancellationException) {
return; // The component was detached before the data was loaded
}
Notification.show("Failed to load the sales summary");
}Because the data is loaded on attach, it’s loaded again if the component is detached and attached again. If that’s not what you want, keep track of whether the data has already been loaded.