`UI` being instantiated over and over again in default Vaadin 14.0.1 app

I filed bug report on this, [ticket # 6,321]
(https://github.com/vaadin/flow/issues/6321).


In previous verions of Vaadin, the UI object represented the entire content space of a web browser’s window/tab. So a Vaadin app would have one, and only one, UI object per window/tab for as long as that browser window/tab was open. We should see UI objects come and go only when a browser window/tab was opened or closed.

➥ Yet in Vaadin 14.0.1 I am seeing the UI object being re-instantiated (or re-initialized?) over and over again for a single opened browser window. Is this a bug?

Use the [Get started with Vaadin]
(https://vaadin.com/start/latest) page to create a new Vaadin 14 app, of the “Plain Java Servlet” flavor. Make the fewest possible changes needed to show the problem, as discussed next.

Add the frontend-maven-plugin via the Maven POM as [discribed on Stack Overflow]
(https://stackoverflow.com/a/57502853/642706).

Edit the MainView.java to add this line:

System.out.println ( "BASIL - new MainView instantiated. " + Instant.now () );

…and disable the @PWA line (as we do not need Progressive Web App features).

package work.basil.example;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;

import java.time.Instant;

/**
 * The main view contains a button and a click listener.
 */
@Route("")
//@PWA(name = "Project Base for Vaadin", shortName = "Project Base")
public class MainView extends VerticalLayout {

    public MainView() {
        System.out.println ( "BASIL - new MainView instantiated. " + Instant.now () );

        Button button = new Button("Click me",
                event -> Notification.show("Clicked!"));
        add(button);
    }
}

Add another view, so we can practice routing.

package work.basil.example;

import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;

import java.time.Instant;

@Route ( "other" )
public class OtherView extends VerticalLayout
{
    public OtherView ( )
    {
        System.out.println ( "BASIL - new OtherView instantiated. " + Instant.now () );
        Text text = new Text ( "Other instantiated at: " + Instant.now () );
        this.add(text);
    }
}

Implement a VaadinServiceInitListener as described [in the manual]
(https://vaadin.com/docs/v14/flow/advanced/tutorial-service-init-listener.html). In that code, add a[ UIInitListener]
(https://vaadin.com/api/platform/14.0.1/com/vaadin/flow/server/UIInitListener.html) to react to a UI object being initialized, as discussed [in the manual]
(https://vaadin.com/docs/v14/flow/advanced/tutorial-ui-init-listener.html).

package work.basil.example;

import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.UIInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;

import java.time.Instant;

public class ApplicationServiceInitListener
        implements VaadinServiceInitListener
{

    @Override
    public void serviceInit ( ServiceInitEvent serviceInitEvent )
    {
        serviceInitEvent.getSource ().addUIInitListener (
                ( UIInitEvent event ) -> {
                    System.out.println ( "BASIL - new UI instantiated. UI id # " + event.getUI ().getUIId () + " " + Instant.now () );
                } );
    }

}

Create a text file, to active the service listener:

/Users/basilbourque/IdeaProjects/uidisappearing/src/main/resources/META-INF/services/com.vaadin.flow.server.VaadinServiceInitListener

…contining the name of our listener class:

work.basil.example.ApplicationServiceInitListener

Run Maven clean & install. Then run the Vaadin app via the bundled Jetty server via Maven plugin, from within IntelliJ (in my case).

Open a web browser pointing to localhost:8080. Click the “Click me” button a few times. Watch the console spew several notices. And manually change the URL to try routing: localhost:8080/other.

When run with only a single web browser loading the app:

BASIL - new UI instantiated. UI id # 0 2019-08-24T02:26:37.336419Z
BASIL - new UI instantiated. UI id # 0 2019-08-24T02:26:37.336562Z
BASIL - new UI instantiated. UI id # 0 2019-08-24T02:26:37.336423Z
BASIL - new MainView instantiated. 2019-08-24T02:26:37.369187Z
BASIL - new UI instantiated. UI id # 1 2019-08-24T02:26:38.230508Z
BASIL - new UI instantiated. UI id # 2 2019-08-24T02:26:39.192811Z
BASIL - new UI instantiated. UI id # 3 2019-08-24T02:26:42.250735Z
BASIL - new UI instantiated. UI id # 4 2019-08-24T02:26:47.289394Z

➥ What is going on with the UI class? Why is it not stable?

➥ Why is the UI id number incrementing? Are new UI objects being instantiated?

➥ Why on #0 is the listener running more than once? As the same UI object being re-initialized? Why, and to what effects?

Enabling that @PWA annotation line has a calming effect on UI.

BASIL - new UI instantiated. UI id # 0 2019-08-24T03:15:04.675171Z
BASIL - new MainView instantiated. 2019-08-24T03:15:04.716987Z
BASIL - new UI instantiated. UI id # 1 2019-08-24T03:15:12.565014Z
BASIL - new MainView instantiated. 2019-08-24T03:15:12.565710Z
BASIL - new UI instantiated. UI id # 2 2019-08-24T03:15:14.205289Z
BASIL - new OtherView instantiated. 2019-08-24T03:15:14.208116Z
BASIL - new UI instantiated. UI id # 3 2019-08-24T03:15:16.190052Z
BASIL - new OtherView instantiated. 2019-08-24T03:15:16.190572Z

➥ Why does @PWA affect UI behavior? Is that another bug or a feature?

➥ But we seem to still be getting new UI instances when routing. Why?

None of this fits my previous understanding of UI class.

This is indeed strange. If you are not refreshing the page, just clicking a button, then it should not recreate new UI according to my understanding. So you may have found a bug (serious one I think). Have you checked what happens if you add @PreserveOnRefresh, which is a new feature in Vaadin 14. Then UI should be retained (also when refreshed).

Tatu Lund:
…Have you checked what happens if you add @PreserveOnRefresh, which is a new feature in Vaadin 14.

Yes, I did try @PreserveOnRefresh.

I added the annotation to both my MainView and my OtherView.

@PWA disabled

With @PWA disabled I get the following after simply having loaded localhost:8080 (did not click the Click Me button, did not navigate to other view).

BASIL - new UI instantiated. UI id # 0 2019-08-24T05:05:31.215158Z
BASIL - new UI instantiated. UI id # 0 2019-08-24T05:05:31.215159Z
BASIL - new UI instantiated. UI id # 0 2019-08-24T05:05:31.215159Z
BASIL - new MainView instantiated. 2019-08-24T05:05:31.228399Z
BASIL - new UI instantiated. UI id # 1 2019-08-24T05:05:32.005648Z
[qtp1639962586-26]

WARN com.vaadin.flow.server.communication.UidlRequestHandler - Invalid security key received from 0:0:0:0:0:0:0:1
BASIL - new UI instantiated. UI id # 2 2019-08-24T05:05:32.337352Z
BASIL - new MainView instantiated. 2019-08-24T05:05:32.337951Z
BASIL - new UI instantiated. UI id # 3 2019-08-24T05:05:32.499706Z
BASIL - new UI instantiated. UI id # 4 2019-08-24T05:05:33.346649Z

Notice the mysterious WARN. I’ve seen only one other time today during all my testing. I assume that number in the message is IPv6 address. I am usig macOS Mojave, so IPv6 is enabled by default.

@PWA enabled

After re-enabling the @PWA (and doing a Maven clean & install, as I do on every one of these tests) I get the following when loading localhost:8080.

BASIL - new UI instantiated. UI id # 0 2019-08-24T05:11:01.775761Z
BASIL - new MainView instantiated. 2019-08-24T05:11:01.788959Z

That was good.

Clicking the “Click Me” button produces no more console output. Good.

Hitting the browser Reload icon produces no more console output. Good.

Problem solved? No. Routing is wonky.

Typing “/other” onto the end of the URL results in several more lines of console output. Bad.

BASIL - new UI instantiated. UI id # 1 2019-08-24T05:12:52.122436Z
BASIL - new UI instantiated. UI id # 2 2019-08-24T05:13:23.816226Z
BASIL - new MainView instantiated. 2019-08-24T05:13:24.214334Z
BASIL - new UI instantiated. UI id # 3 2019-08-24T05:13:24.704560Z
BASIL - new OtherView instantiated. 2019-08-24T05:13:24.705400Z
BASIL - new UI instantiated. UI id # 4 2019-08-24T05:13:25.242077Z
BASIL - new OtherView instantiated. 2019-08-24T05:13:25.630358Z

Besides the multiple UI instances, another problem is how the MainView that we are leaving gets reinstantiated while routing to /other (OtherView).

if there is only one Tab on browser opened, then there is only one UI instance created and mapped to that Tab. The UI Id is index of that Tab and should not change if you don’t send a new http request to localhost:8080 from that Tab.
Because you put @PreserveOnRefresh so The UI Id should not increase when you refresh page.

Did you open url in other browsers or other local PCs accessing to your testing PC that you forget to close ?

If not then this is only a bug on Vaadin.

Instead of localhost:8080, can you try accessing the application via 0.0.0.0:8080 ?

Syam Pillai:
Instead of localhost:8080, can you try accessing the application via 0.0.0.0:8080 ?

With @PWA enable, using http://[::1]
:8080/ gives me same results as with IPv4: When I use routing to goto http://[::1]
:8080/other I get UI re-initialized/re-instantiated.

BASIL - new UI instantiated. UI id # 0 2019-08-24T17:53:00.240552Z
BASIL - new MainView instantiated. 2019-08-24T17:53:00.255365Z
BASIL - new UI instantiated. UI id # 1 2019-08-24T17:53:12.330589Z
BASIL - new MainView instantiated. 2019-08-24T17:53:12.744920Z
BASIL - new UI instantiated. UI id # 2 2019-08-24T17:53:36.636009Z
BASIL - new MainView instantiated. 2019-08-24T17:53:37.029630Z
BASIL - new UI instantiated. UI id # 3 2019-08-24T17:53:40.583674Z
BASIL - new OtherView instantiated. 2019-08-24T17:53:40.587653Z
BASIL - new UI instantiated. UI id # 4 2019-08-24T17:53:57.398895Z
BASIL - new UI instantiated. UI id # 5 2019-08-24T17:53:57.680031Z
BASIL - new MainView instantiated. 2019-08-24T17:53:58.072961Z
BASIL - new UI instantiated. UI id # 6 2019-08-24T17:54:13.493352Z
BASIL - new MainView instantiated. 2019-08-24T17:54:13.877363Z
BASIL - new UI instantiated. UI id # 7 2019-08-24T17:54:14.952634Z
BASIL - new OtherView instantiated. 2019-08-24T17:54:14.953239Z
BASIL - new UI instantiated. UI id # 8 2019-08-24T17:54:15.948471Z
BASIL - new OtherView instantiated. 2019-08-24T17:54:16.361519Z

Please someone verify this troubling behavior.

Quite easy to do:

  • Make a fresh project.
  • Configure your Node.js & npm.
  • Add a few calls to System.out.println.
  • Add a UI init listener.

I had a similar problem in an earlier version when I accessed the application via localhost. The problem was that Vaadin uses cookies to maintain sessions and localhost access to Chrome browser under Linux (don’t know about its behaviour in other OSes) was not keeping cookies. However, it uses cookies if I access using 0.0.0.0 and that’s why I suggested that. Can you verify that your browser under macOS is supporting cookies with that sort of access?

Syam Pillai:
I had a similar problem in an earlier version when I accessed the application via localhost. The problem was that Vaadin uses cookies to maintain sessions and localhost access to Chrome browser under Linux (don’t know about its behaviour in other OSes) was not keeping cookies. However, it uses cookies if I access using 0.0.0.0 and that’s why I suggested that. Can you verify that your browser under macOS is supporting cookies with that sort of access?

I created a virtual machine using Parallels, to get a fresh clean environment. Running macOS Mojave 10.14.6 with OpenJDK 11.0.4, Community edition of IntelliJ 2019.2.1. Downloaded a new Vaadin 14.0.1 “Plain Java Servlet” project. Pasted the snippet for the POM to install Node.js/npm. Added the service listener, in which I install a UI init listener. Just as described in my original post on this page. In this round of testisg for this post, I left @PWA intact on MainView, and did not add @PreserveOnRefresh on either of my views.

I tried a different browser too, current Firefox 68.0.2 instead of Safari. I cleared all history from the browser. I pointed Firefox to http://0.0.0.0:8080/ as you suggested. I was confused by this, as I always thought of 0.0.0.0 being special, reserved for LAN routers and such. But I gave it a shot. Same problem where routing to my second view with typing /other in the URL triggers a UIInitEvent. Apparently the UI object is either re-initialized or replaced with a fresh instance — either way, that’s bad.

BASIL - `UIInitEvent` for UI id # 0. 2019-08-25T06:22:37.406351Z
BASIL - constructor `MainView`.
BASIL - `UIInitEvent` for UI id # 1. 2019-08-25T06:22:52.197492Z
BASIL - Constructor `OtherView`. 2019-08-25T06:22:52.199454Z

To further explore your theory about cookie management misbehaving within the browser because of loopback or localhost, I used Firefox on my real Mac hosting to first clear its history, and then to connect to the IP address of the virtual machine Mac running Jetty within IntelliJ, using URL http://192.168.0.186:8080/. Again, routing to /other generated another UIInitEvent.

BASIL - `UIInitEvent` for UI id # 0. 2019-08-25T06:28:05.788582Z
BASIL - constructor `MainView`.
BASIL - `UIInitEvent` for UI id # 1. 2019-08-25T06:30:06.134719Z
BASIL - Constructor `OtherView`. 2019-08-25T06:30:06.135540Z

So I think I ruled out cookie mismanagement as the problem. I used different browsers (Firefox vs Safari), and used 0.0.0.0 rather than localhost or 127.0.0.1, and used a regular IP address on my real Mac to reach my virtual Mac running Vaadin.

Yet another test: I added @PreserveOnRefresh to both my views.

@PreserveOnRefresh
@Route("")
@PWA(name = "Project Base for Vaadin", shortName = "Project Base")
public class MainView extends VerticalLayout {

and

@PreserveOnRefresh
@Route("other")
public class OtherView extends VerticalLayout {

Rather than run Jetty from within IntelliJ, I quit IntelliJ, copied the WAR file to a newly-installed Apache Tomcat 9.0.24 within my Parallels virtual machine. Same behavior where routing to /other from my Safari on the host (real) Mac triggers a UIInitEvent.

BASIL - `ServiceInitEvent`.2019-08-25T06:53:28.256149Z
BASIL - `UIInitEvent` for UI id # 0. 2019-08-25T06:53:28.370298Z
BASIL - constructor `MainView`.
BASIL - `UIInitEvent` for UI id # 1. 2019-08-25T06:58:21.677119Z
BASIL - Constructor `OtherView`. 2019-08-25T06:58:21.678951Z

Looks like a bug. Need to wait for a response from the Vaadin team.

I was thinking maybe this troubling UI re-initialization/re-instantiation was because of my manually typing in the URL bar of the web browsers. The browsers may be doing active calls to the server as part of the URL suggesting feature.

So I revamped my example app to using links.

@PreserveOnRefresh
@Route("")
@PWA(name = "Project Base for Vaadin", shortName = "Project Base")
public class MainView extends VerticalLayout {

    public MainView() {
        System.out.println("BASIL - constructor `MainView`.");
        Button button = new Button("Click me " + Instant.now(),
                event -> Notification.show("Clicked!"));
        add(button);

        // Link back to main view.
        String route = UI.getCurrent().getRouter().getUrl(OtherView.class);
        Anchor link = new Anchor(route, "Go to other view.");
        this.add(link);
    }
}

…and…

@PreserveOnRefresh
@Route("other")
public class OtherView extends VerticalLayout {
    public OtherView() {
        System.out.println("BASIL - Constructor `OtherView`. " + Instant.now());
        H1 h1 = new H1("Other. " + Instant.now());
        this.add(h1);

        // Link back to main view.
        String route = UI.getCurrent().getRouter().getUrl(MainView.class);
        Anchor link = new Anchor(route, "Go to main view.");
        this.add(link);
    }
}

But no change in behavior. Clicking the link to visit the opposite view via Routing triggers another UIInitEvent.

BASIL - `ServiceInitEvent`.2019-08-25T20:30:23.716983Z
BASIL - `UIInitEvent` for UI id # 0. 2019-08-25T20:30:23.842385Z
BASIL - `UIInitEvent` for UI id # 0. 2019-08-25T20:31:09.774154Z
BASIL - constructor `MainView`.
BASIL - `UIInitEvent` for UI id # 1. 2019-08-25T20:31:27.056157Z
BASIL - Constructor `OtherView`. 2019-08-25T20:31:27.057202Z
BASIL - `UIInitEvent` for UI id # 2. 2019-08-25T20:31:32.022503Z
BASIL - constructor `MainView`.
BASIL - `UIInitEvent` for UI id # 3. 2019-08-25T20:31:39.179601Z
BASIL - Constructor `OtherView`. 2019-08-25T20:31:39.180369Z

Furthermore, notice the constructors running on MainView and OtherView despite my having annotated them with @PreserveOnRefresh.

No, web browsers do not invoke web pages while typing other than showing entries from the cache.

Syam Pillai:
No, web browsers do not invoke web pages while typing other than showing entries from the cache.

Incorrect.

Typing an “l” (for localhost) in Safari web browser’s URL address bar causes my Vaadin app to run a new session, with output on the console. This happens in the background, with no Vaadin app yet loaded into the browser window.

See screenshot attached.

And try it for yourself.

17811735.png

To my understanding, a new UI should indeed be created when navigating “manually”, e.g. not using the Vaadin router. Effectively, the page is reloaded, so a new UI is created.

I believe @PreserveOnRefresh also intentionally creates a new UI, while retaining all other components.

If you set the router-link attribute on your anchor, getElement().setAttribute("router-link", "");, new UI instances should not be created.

Erik Lumme:

I believe @PreserveOnRefresh also intetionally creates a new UI, while retaining all other components.

I am just now experimenting with showing that UI object is indeed lost when clicking the browser Reload button, despite using @PreserveOnRefresh. I was going to report that as a bug.

How does it make sense to wipe out the UI yet preserve other content? I don’t see the logic.

And losing the UI object also means losing any state I may have attached to the UI via ComponentsUtil.setData, which is akin to setAttribute on the VaadinSession and on the VaadinContext. I always thought of UI as the browser window/tab-specific scope, just as VaadinSession is the user-specifc scope, and VaadinContext is app-wide scope.

➥ Where should I store window-level state if not on the UI?

This cavalier attitude about replacing the UI object is puzzling to me. In previous generations of Vaadin, when we wrote a Vaadin app by subclassing UI, our UI object was the core of app (well, the core of each window of our app), and stable for the entire time user kept window open.

Effectively, the page is reloaded, so a new UI is created.

I believe @PreserveOnRefresh also intentionally creates a new UI, while retaining all other components.

Those two sentences seem like a logical contradiction to me. The whole point of @PreserveOnRefresh is to not reload the page. So why intentionally create a new UI?

This was my understanding as well. I have thought that @PreserveOnRefresh only means that the View instance (not the UI!) would be reused (aka not re-initialized) upon refreshing, as I believed that the UI instance remained the same for each tab anyway.

The question that I have to ask myself now is one that Basil has posed too:

Where would I store window-level state if not on the UI?

There are many reasons why Session Scope is not optimal for storing window scoped states. But as long as the UI is being reinstantiated upon any navigation or page-refresh, the UI cannot be used at all for storing data/states.

Edit: In the [docs for @PreserveOnRefresh]
(https://vaadin.com/docs/v14/flow/advanced/tutorial-preserving-state-on-refresh.html#preconditions-and-limitations) it says:

Vaadin 10 and later does not preserve the UI instance between refreshes. The view is detached from its previous UI and then attached to a fresh UI instance on refresh.

Ok i guess this is intended. But it also says this:

Another use case is supporting browser tab-specific “sessions” as an alternative to the standard cookie-based session.

This “tab-specific session” is exactly what I want. But I think what is meant here is more like a view-specific session. As soon as you navigate to another view, all your “tab-specific” data is lost even if you stayed in the application, in the same tab.

In order to achieve a window-level storage, you may leverage a public class MainLayout extends VerticalLayout implements RouterLayout (or something similar) as the base RouterLayout for all your @Route annotations.

Since routes which use the same RouterLayout class share the instance of that RouterLayout, you can use that instance to store data that should be shared between views. As refreshing the page will still recreate the UI and associated components, you can annotate the MainLayout with @PreserveOnRefresh to keep the whole Component tree through the refresh. This of course means that things that should not be retained, need to be manually cleaned.

Here’s a quick and dirty approach for a shared RouterLayout which provides an interface for the components to implement. The interface retrieves the MainLayout instance.

public class MainLayout extends VerticalLayout implements RouterLayout {

    public interface Retriever extends HasElement {
        default Optional<Component> getMainLayout() {
            Component parent = getElement().getComponent().orElse(null);

            while (parent != null && !parent.getClass().equals(MainLayout.class)) {
                parent = parent.getParent().orElse(null);
            }

            return Optional.ofNullable(parent);
        }
    }
}

here is my topic about UI.
https://vaadin.com/forum/thread/17748771/when-an-ui-instance-is-created-in-vaadin-flow

Note that there is now a new ticket filed in GitHub issues related to this topic

https://github.com/vaadin/flow/issues/6395