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.
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
Any update on this? In Vaadin 7, I could logout and cycle through UI to do special logout activity for each UI, but now, because of the multiple UI instances, even for one tab,my code is quite confused. In other words, on logout, I expect only one UI because only 1 tab open, but I see 2-4 UI.