Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI being initialized (instantiated perhaps) repeatedly in default Vaadin 14.0.1 app #6321

Closed
basil-bourque opened this issue Aug 25, 2019 · 3 comments
Milestone

Comments

@basil-bourque
Copy link

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 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.

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. In that code, add a UIInitListener to react to a UI object being initialized, as discussed in the manual.

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.

For more discussion with many examples and variations enabling/disabling @PWA & @PreserveOnRefresh, see this thread on the Vaadin Forums: UI being instantiated over and over again in default Vaadin 14.0.1 app.

@joheriks
Copy link
Contributor

Regarding the UI being re-instantiated with @PreserveOnRefresh, this is a documented limitation in Vaadin 14:
https://vaadin.com/docs/v14/flow/advanced/tutorial-preserving-state-on-refresh.html#preconditions-and-limitations

@Legioth
Copy link
Member

Legioth commented Sep 4, 2019

When you use @PWA, there will be a Service Worker installed in the browser for that domain:port pair. The purpose of that Service Worker is to serve the offline page even if the server cannot be reached. For this to work, the browser will preserve the Service Worker indefinitely.

When you remove the @PWA annotation (or run another application on the same localhost port), the browser will still try to use the old Service Worker. This will cause a couple of requests to be sent even Vaadin doesn't have any special handling for those requests now when there isn't any @PWA annotation that defines what to respond. Instead, those additional requests will each trigger a regular 404 response.

You can get rid of these requests by manually removing the Service Worker in the browser. In Chrome, this is done through the Application tab in the developer tools. In Firefox, it's done through the about:serviceworkers page. After doing this, you should get the same observable behaviour as with the @PWA annotation present. #6394 has been opened to investigate the possibility of avoiding those confusing requests in cases when @PWA has been present but is subsequently removed.

Vaadin handles the 404 errors by showing the error view. By default, this is com.vaadin.flow.router.RouteNotFoundError but the application developer can also override this. This error view is represented as a Vaadin component tree which is bootstrapped and rendered in the same way as an existing route. This is so that an application's custom 404 view can still contain the same navigation elements and such as are used for regular @Route views.

The Service Worker will trigger multiple additional requests that all cause 404 responses which explains why there are a total of 7 UI instances created. The reason you get multiple UIs with #0 in the beginning is that the very first requests are sent without a valid session id cookie. This means that each request will cause a new servlet session to be created and then there will be one UI instance created for each session. Each response will include a cookie for the session that was created for that request. Any subsequent request will then stick to whichever of those cookies that the browser ended up actually using. Once this has happened, the remaining 404 pages will have their UI instances created in the same session and you will thus get incremental ids. You can confirm this by also logging the session id in addition to the UI id.

Then we finally get to the last open questions about new UI instances when routing. The way this works is that by default, there will be a new UI instance every time the browser loads a new page, i.e. whenever the browser requests a new HTML document to show. If you are navigating using RouterLink, UI.navigate methods, then Vaadin will internally request updates to the rendered component tree without loading a new HTML document. If you instead navigate with a regular anchor, refresh in the browser or type an address into the browser's address bar, then the browser will request a new HTML document and thus also cause a new UI instance to be created. This is also how things worked in Vaadin 7 and 8 (except that RouterLink didn't exist).

One difference compared to Vaadin 7/8 is how @PreserveOnRefresh is implemented. In those previous versions, @PreserveOnRefresh was applied on the UI level so that the whole UI instance was reused even when a new HTML document was generated as long as you remained within the same browser tab. In Vaadin 14, @PreserveOnRefresh is instead slightly more granular so that one @Route class may be preserved while another one is always recreated whenever the browser requests a new HTML document. This means that the UI instance is always recreated even though selected parts of the applications own components are preserved.

I assume that the log examples you showed where a new UI instance was created through routing were triggered by one of the navigation methods that makes the browser request a new HTML document. Based on that, I would say that all parts are working as they should, even though I also understand that some of the details are not quite obvious.

This in turn means that there isn't really anything to fix in Vaadin (except for the separately tracked #6394). The only thing that could potentially be done would be to improve documentation to make it easier to understand what goes on. On the other hand, I don't have any good idea on exactly what parts of the documentation to tweak in a meaningful way. Based on this, I will close this issue but I would be fine with opening it again if there's some detail that still seems fishy or if there are any concrete ideas on how to improve the documentation.

@Legioth Legioth closed this as completed Sep 4, 2019
OLD Vaadin Flow bugs & maintenance (Vaadin 10+) automation moved this from Needs triage to Closed - pending release Sep 4, 2019
@Legioth
Copy link
Member

Legioth commented Sep 4, 2019

For reference, there's also #6395 about the possibility that @PreserveOnRefresh would also be applied on the UI level so that navigation that causes a new HTML document to be requested would not cause a new UI instance to be created as long as the same browser tab is used.

@mehdi-vaadin mehdi-vaadin added this to the Abandoned milestone Sep 16, 2019
@caalador caalador moved this from Closed - pending release to Closed - released in OLD Vaadin Flow bugs & maintenance (Vaadin 10+) Dec 18, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants