I’ve written a small Spring Boot/Vaadin application that displays a simple UI to take user input and make a call to another service that takes some time to run. When the task is submitted, I’m displaying a progress dialog that shows a progress bar, a message informing the user what is going on and a close button to allow them to close the dialog when the job completes. I’m using a ListenableFuture
to be notified when the task is done.
I can get the dialog to appear with status of “executing” and the progress bar doing its thing, but when the task is done (I have debug statements going to the console to let me know), it’s not triggering the logic to update the status message and enable the close button. I can’t figure out what I’m doing wrong.
Here’s the code:
MainView1.java
@Route("rescheduleWorkOrders1") @CssImport("./styles/shared-styles.css") public class MainView1 extends VerticalLayout { ... private final BackendService service; public MainView1(BackendService service) { this.service = service; configureView(); addSubmitButton(); bindFields(); } private void addSubmitButton() { Button submit = new Button("Submit", this::submit); add(submit); } private void submit(ClickEvent<?> event) { UserData data = binder.getBean(); ListenableFuture<ResponseEntity<String>> future = service.executeTask(data); ProgressDialog dialog = new ProgressDialog(future); dialog.open(); } private void configureView() { ... } private void bindFields() { ... } }
ProgressDialog.java
public class ProgressDialog extends Dialog { private final ListenableFuture<ResponseEntity<String>> future; private ProgressBar bar; private Paragraph message; private Button close; public ProgressDialog(ListenableFuture<ResponseEntity<String>> future) { super(); this.future = future; configureView(); this.future.addCallback(new ListenableFutureCallback<>() { @Override public void onSuccess(ResponseEntity<String> result) { message.setText("Task complete. Status: " + result.getStatusCode()); bar.setVisible(false); close.setEnabled(true); } @Override public void onFailure(Throwable ex) { message.setText(ex.getMessage()); bar.setVisible(false); close.setEnabled(true); } }); } private void configureView() { bar = new ProgressBar(); bar.setIndeterminate(true); bar.setVisible(true); message = new Paragraph("Executing task ..."); close = new Button("Close", this::close); close.setEnabled(false); add(bar, message, close); } private void close(ClickEvent<?> event) { this.close(); } }
BackendService.java
@Service public class BackendService { @Async public ListenableFuture<ResponseEntity<String>> executeTask(UserData data) { ... RestTemplate template = new RestTemplate(); ResponseEntity<String> response = template.postForEntity(uri, entity, String.class); System.out.println(response); return AsyncResult.forValue(response); } }
Note: I do have @EnableAsync
specified in a @Configuration
annotated class.
Advertisement
Answer
When dealing with asynchronous code in Vaadin you need to:
- Use
UI#access
when updating the UI outside an active request. This acquires a lock on the UI, to prevent it being updated by two threads simultaneously. - Enable server push by adding the
@Push
annotation to your main layout or view. This allows the server to push updates to the client even if no request is active.
Without the former, you can get ConcurrentModificationException
s in the best case, and very subtle bugs in the worst.
Without the latter, the changes will be applied (i.e. dialog closed), but the changes will only be sent to the client the next time the client sends a request. I believe this is your main issue.
More information can be found in the documentation.