Vaadin: Update UI after data returned
Asked Answered
E

2

2
@SpringUI
public class VaadinUI extends UI {
  ...
  String sql = "SELECT * FROM table1";
  button.addClickListener(e -> layout.addComponent(new Label(service.evalSql(sql))));
  ...

Currently, when button is pressed, the page waits for evalSql() to get a result back from the database before adding a new Label.

How can I change this so, when button is pressed, a new Label is immediately added, set to an initial placeholder string ("Fetching result..") but updated to the result string after the database returns something?

Essentiality answered 15/6, 2018 at 17:51 Comment(0)
P
6

The good news is that what you want, to have a widget in your Vaadin user-interface later updated by work done in the background on the server without blocking the UI's responsiveness to the user, can be done. It can be done very well with Vaadin and its Java-based backend.

The bad news is that if you are new to concurrency and threading, you have a learning curve to climb.

Asynchronous

The technical term for wanting your app to do something in the background and check back later without blocking is: asynchronous update.

We can accomplish this in Java using threads. Spawn a thread to run your SQL service code. When that code finishes the database work, that code posts a request by calling UI::access(Runnable runnable) to have the original user-interface (UI) thread update the Label widget.

Push technology

As discussed in the Answer by Lund updating of the Label widget requires Push Technology to update the browser from a server-side generated event. Fortunately, Vaadin 8 and later has excellent support for Push and makes instituting Push in your app extraordinarily easy.

Tip: Push in general, and WebSocket especially, has evolved greatly in recent years. Using the latest generation of Servlet container will improve your experience. For example, if using Tomcat I recommend using the latest version of Tomcat 8.5 or 9.

Threads

Java has excellent support for threading. Much of the necessary work is handled for you by the Executor framework built into Java.

If you are new to threading, you have some serious learning ahead of you. Start by studying the Oracle Tutorial on concurrency. Eventually you'll need to read and re-read a few times the excellent book Java Concurrency in Practice by Brian Goetz et al.

ServletContextListener

You will likely want to set-up and tear-down your thread-juggling executor service as your Vaadin app launches and exits. The way to do that is to write a class separate from your Vaadin servlet class. This class must implement the ServletContextListener. You can easily do that by implementing the two required methods, and annotating the class with @WebListener.

Be sure to tear-down the executor service. Otherwise the background threads it manages may survive the shutdown of your Vaadin app and possibly even the shutdown of your web container (Tomcat, Jetty, etc.), continuing to run indefinitely.

Never access widget from background thread

A key idea in this work: Never ever access any Vaadin UI widget directly from any background. Do not call any methods on the UI widgets, nor access any values, from any widget in code running in a background thread. The UI widgets are not thread-safe (making a user-interface technology thread-safe is terribly difficult). You might get away with such a background call, or terrible things may happen at runtime.

Java EE

If you happen to be using a full-blown Jakarta EE (formerly known as Java EE) server rather than either a web container (such as Tomcat or Jetty) or a Web Profile server (such as TomEE), then the work discussed above with the executor service and ServletContextListener is done for you. Use the features defined in Java EE 7 and later: JSR 236: Concurrency Utilities for JavaTM EE

Spring

Your Question is tagged with Spring. Spring may have features to help with this work. I don’t know, as I am not a Spring user. Perhaps Spring TaskExecutor?

Search Stack Overflow

If you search Stack Overflow you will find that all these topics have been addressed.

I have already posted two full example apps demonstrating Vaadin with Push:

  • A contrived minimalist example, just to give you a taste of what is involved. That particular approach should never be used in real work, as noted in that Answer.
  • For real work, see a more complicated example app posted in this Answer.

Complete example

Start with the Vaadin 8.4.3 app generated by the Maven archetype, vaadin-archetype-application provided by the Vaadin Ltd. company.

package com.basilbourque.example;

import javax.servlet.annotation.WebServlet;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

/**
 * This UI is the application entry point. A UI may either represent a browser window
 * (or tab) or some part of an HTML page where a Vaadin application is embedded.
 * <p>
 * The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
 * overridden to add component to the user interface and initialize non-component functionality.
 */
@Theme ( "mytheme" )
public class MyUI extends UI {

    @Override
    protected void init ( VaadinRequest vaadinRequest ) {
        final VerticalLayout layout = new VerticalLayout();

        final TextField name = new TextField();
        name.setCaption( "Type your name here:" );

        Button button = new Button( "Click Me" );
        button.addClickListener( e -> {
            layout.addComponent( new Label( "Thanks " + name.getValue() + ", it works!" ) );
        } );

        layout.addComponents( name , button );

        setContent( layout );
    }

    @WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
    @VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
    public static class MyUIServlet extends VaadinServlet {
    }
}

enter image description here

As discussed above, we need to assign your SQL-service work as a task to be done on a background thread. The Executor framework in Java 5 and later does all the heavy-lifting for such thread work. We need to establish an executor service backed by a thread pool to do all the updating of all the new labels being added on all the users’ web browser windows. The question is where do we setup, store, and teardown that executor service object?

We want the executor service to be available for the entire lifecycle of our web app. Before the first user request arrives to our freshly-launched web app, we want to setup the executor service. And when we are trying to shutdown our web app, we need to teardown that executor service, so that the threads in its backing thread pool are terminated. How do we tie into the lifecycle of our Vaadin web app?

Well, Vaadin is an implementation of Java Servlet, albeit a very large and sophisticated servlet. In Servlet terminology, your web app is known as a “context”. The Servlet specification requires that all Servlet containers (such as Tomcat, Jetty, etc.) notice any class marked as a listener to certain events. To take advantage of this we must add another class to our Vaadin app, a class implementing the ServletContextListener interface.

If we annotate our new class as @WebListener, the Servlet container will notice this class, and when launching our web app will instantiate our listener object, and then call its methods at the appropriate times. The contextInitialized method is called after the servlet has been properly initialized but before any incoming web browser requests have been handled. The contextDestroyed method is called after the last web browser request has been handled, after that last response has been sent back to the user.

So our class implementing the ServletContextListener is the perfect place to setup and teardown our executor service with its backing thread pool.

One more problem: After setting up our executor service, where do we store a reference to be found and used later in our Vaadin servlet when the users are adding their Label objects? One solution is to store the executor service reference as an “attribute” in the “context” (our web app). The Servlet spec requires that every Servlet container provide each context (web app) with a simple key-value collection where the key is a String object and the value is an Object object. We can invent some string to identify our executor service, and then our Vaadin servlet can later do a loop-up to retrieve the executor service.

Ironically enough, that discussion above is longer than the actual code!

package com.basilbourque.example;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@WebListener
// Annotate to instruct your web container to notice this class as a context listener and then automatically instantiate as context (your web app) lanuches.
public class MyServletContextListener implements ServletContextListener {
    static final public String executorServiceNameForUpdatingLabelAfterSqlService = "ExecutorService for SQL service update of labels";

    @Override
    public void contextInitialized ( final ServletContextEvent sce ) {
        // Initialize an executor service. Store a reference as a context attribute for use later in our webapp’s Vaadin servlet.
        ExecutorService executorService = Executors.newFixedThreadPool( 7 );  // Choose an implementation and number of threads appropriate to demands of your app and capabilities of your deployment server.
        sce.getServletContext().setAttribute( MyServletContextListener.executorServiceNameForUpdatingLabelAfterSqlService , executorService );
    }

    @Override
    public void contextDestroyed ( final ServletContextEvent sce ) {
        // Always shutdown your ExecutorService, otherwise the threads may survive shutdown of your web app and perhaps even your web container.

        // The context addribute is stored as `Object`. Cast to `ExecutorService`.
        ExecutorService executorService = ( ExecutorService ) sce.getServletContext().getAttribute( MyServletContextListener.executorServiceNameForUpdatingLabelAfterSqlService );
        if ( null != executorService ) {
            executorService.shutdown();
        }

    }
}

Now, back to our Vaadin app. Modify that file:

  • Annotate the Servlet with @Push to engage Vaadin’s ability to have a server-side generated event update the user-interface widgets.
  • Modify the creation of each Label.
    • Change the initial text for the Label to include "Created: " with current date-time.
    • Move the instantiation to its own line.
  • Add behavior so that after instantiating a new Label, we retrieve our executor service from the context attribute collection, and submit to it a Runnable that will eventually be run to do our SQL service. To simulate the work of that SQL service, we sleep the background thread random number of seconds under half a minute. Upon waking, that background thread asks our UI object representing our web app’s content displayed in the web browser to schedule yet another Runnable to eventually be run on its main user-interface thread. As discussed above, never directly access a UI widget from a background thread! Always politely ask the UI object to schedule widget-related work on its own timetable in its own thread.

If you are new to threading and concurrency, this may be daunting. Study this code, and it some time to sink in. You could structure this in other manners, but I wanted to make it simple here for teaching purposes. Focus not on the structure/arrangement of the code but on the idea of:

  • User clicks button, an event in main Vaadin UI thread.
  • Code on the button submits to an executor service a task (a Runnable) to be run later in a background thread.
  • That background thread, when eventually run, calls on your SQL service to get some work done. When done, we post a request (another Runnable) to the UI to do some widget-related work (our Label text updating) on our behalf.
  • When convenient to the UI, when it is not too busy handling other user-related events generated in the user-interface, the UI gets around to running our Runnable to actually modify the text of the Label that was added a while ago.

Here is our modified Vaadin app.

package com.basilbourque.example;

import javax.servlet.ServletContext;
import javax.servlet.annotation.WebServlet;

import com.vaadin.annotations.Push;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * This UI is the application entry point. A UI may either represent a browser window
 * (or tab) or some part of an HTML page where a Vaadin application is embedded.
 * <p>
 * The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
 * overridden to add component to the user interface and initialize non-component functionality.
 */
@Push  // This annotation enables the Push Technology built into Vaadin 8.4.
@Theme ( "mytheme" )
public class MyUI extends UI {

    @Override
    protected void init ( VaadinRequest vaadinRequest ) {
        final VerticalLayout layout = new VerticalLayout();

        final TextField name = new TextField();
        name.setCaption( "Type your name here:" );

        Button button = new Button( "Click Me" );
        button.addClickListener( ( Button.ClickEvent e ) -> {
            Label label = new Label( "Thanks " + name.getValue() + ", it works!" + " " + ZonedDateTime.now( ZoneId.systemDefault() ) );  // Moved instantiation of `Label` to its own line so that we can get a reference to pass to the executor service.
            layout.addComponent( label );  // Notes current date-time when this object was created.

            //
            ServletContext servletContext = VaadinServlet.getCurrent().getServletContext();
            // The context attribute is stored as `Object`. Cast to `ExecutorService`.
            ExecutorService executorService = ( ExecutorService ) servletContext.getAttribute( MyServletContextListener.executorServiceNameForUpdatingLabelAfterSqlService );
            if ( null == executorService ) {
                System.out.println( "ERROR - Failed to find executor serivce." );
            } else {
                executorService.submit( new Runnable() {
                    @Override
                    public void run () {
                        // Pretending to access our SQL service. To fake it, let's sleep this thread for a random number of seconds.
                        int seconds = ThreadLocalRandom.current().nextInt( 4 , 30 + 1 ); // Pass ( min , max + 1 )
                        try {
                            Thread.sleep( TimeUnit.SECONDS.toMillis( seconds ) );
                        } catch ( InterruptedException e ) {
                            e.printStackTrace();
                        }
                        // Upon waking, ask that our `Label` be updated.
                        ZonedDateTime zdt = ZonedDateTime.now( ZoneId.systemDefault() );
                        System.out.println( "Updating label at " + zdt );
                        access( new Runnable() {  // Calling `UI::access( Runnable )`, asking that this Runnable be run on the main UI thread rather than on this background thread.
                            @Override
                            public void run () {
                                label.setValue( label.getValue() + " Updated: " + zdt );
                            }
                        } );
                    }
                } );
            }
        } );

        layout.addComponents( name , button );

        setContent( layout );
    }


    @WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
    @VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
    public static class MyUIServlet extends VaadinServlet {
    }
}

When doing such asynchronous threaded work, the exact order of execution cannot be predicted. You do not know exactly when and in which order the background threads well be executing. You do not know when the UI object will get to our request to update the Label object’s text. Notice in this screenshot that while running this app, different Label objects were updated at different times in an arbitrary order.

enter image description here

Related Questions:

Pruritus answered 16/6, 2018 at 6:31 Comment(0)
J
4

You need to use @Push in your UI and update the content of the Label when query has been completed using UI.access(..). There is good documentation about it with some examples here:

https://vaadin.com/docs/v8/framework/advanced/advanced-push.html

Juni answered 15/6, 2018 at 18:0 Comment(4)
Push seems inappropriate here. Push is for an event initiated on the server-side. In this Question, it seems the user is initiating the event. The trick is that the new Label object added as a result of the user event needs to be updated later asynchronously.Pruritus
@BasilBourque , you need Push also in that scenario.Juni
@BasilBourque The server event is, to update your UI as soon as the data is ready on the server. So it's completely the correct use case.Masaccio
@AndréSchild Yes, thanks, I understand now. When the background thread on the server doing the SQL work eventually calls UI::access(Runnable runnable) where the Runnable will update the Label widget, that becomes a server-side-initiated event that requires Push technology.Pruritus

© 2022 - 2024 — McMap. All rights reserved.