Skip to content

DynamicTheme is not initialized in sessions that did not serve index.html #172

Description

@javier-godoy

DynamicTheme stores its state in a VaadinSession attribute that is only set by DynamicTheme.initialize(...), which runs while index.html is being generated (from AppShellConfigurator.configurePage or an IndexHtmlRequestListener). If a UI runs in a session that never served index.html, the attribute is missing and:

  • DynamicTheme.prepare(...) and DynamicTheme.apply(...) throw IllegalStateException: Dynamic theme switching has not been initialized
  • DynamicTheme.getCurrent() returns null
  • TabbedDemo hides the theme selector without any message, because it checks isFeatureInitialized()

This happens in production, not in local development. Two situations cause it:

  1. The server restarts or scales to zero while a page is open. The session is lost, and the next request from the open tab runs in a new session that never went through index.html.
  2. index.html is served from a cache (browser, proxy or CDN), so configurePage / the index HTML listener never runs for the session that serves the UI.

Stack trace (commons-demo 5.5.0-SNAPSHOT, Vaadin 25.3.0)

java.lang.IllegalStateException: Dynamic theme switching has not been initialized
    at com.flowingcode.vaadin.addons.demo.DynamicTheme.assertFeatureInitialized(DynamicTheme.java:87)
    at com.flowingcode.vaadin.addons.demo.DynamicTheme.prepare(DynamicTheme.java:213)
    at ...(attach listener calling DynamicTheme.prepare(menuBar))
    at com.vaadin.flow.component.ComponentUtil.onComponentAttach(ComponentUtil.java:316)
    ...
    at com.vaadin.flow.component.UI.browserNavigate(UI.java:2205)
    ...
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocations(...)

Steps to reproduce

  1. Call DynamicTheme.LUMO.initialize(settings) in configurePage, and call DynamicTheme.prepare(component) on a component in the view.
  2. Open the app in production mode.
  3. Restart the server without reloading the browser tab. (Alternatively, serve index.html from a cache.)
  4. Interact with or navigate in the open tab.

Expected: the view works with the default theme.
Actual: IllegalStateException from prepare (or a null from getCurrent()).

Suggested fix

Don't make "initialized" depend on the index.html request of the current session. For example:

  • initialize(...) could also save the chosen default somewhere that is not tied to one session (such as the VaadinContext, or a static field set when DynamicThemeInitializer runs), and getCurrent() could fall back to that default when the session attribute is missing, storing it in the session at that point.
  • Alternatively, DynamicThemeInitializer could register a SessionInitListener that sets the default theme for every new session.

Either way, prepare/apply would no longer throw in these situations. The page may still have a different theme stylesheet than the session thinks (for example, aura.css from an earlier session). That is harmless, because apply() enables and disables the <link> elements on the page regardless of which one is currently active.

Workaround

In the application, register a SessionInitListener that sets DynamicTheme.LUMO (or whichever theme is the default) as the session attribute when it is missing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions