Updating elements in a combobox

I have an app where i get the values to display in a combo box from the database when the application starts up. There is a REST endpoint that allows you to add additional values to the database. Now i want the app the reflect the newly added values in the combo box.
It sounds like i need to do something with the UI object since I need to update something that needs reflected on the UI outside of the UI thread, but i’m a little unclear on how to do that.

Right now I have my UI component implementing an interface that has an update() method, and the component registers itself as a listener. When the REST call is made and a new value added, i loop through the listeners calling the update() method

            UI.getCurrent().access(() -> {
                listeners.stream().forEach(listener -> listener.update());                
            });

but i get the following error:

java.lang.NullPointerException: Cannot invoke “com.vaadin.flow.component.UI.access(com.vaadin.flow.server.Command)” because the return value of “com.vaadin.flow.component.UI.getCurrent()” is null

so i am sure I am missing some basic priniciple on how to do this. Any help would be appreciated. Thank you!

You cannot access the current UI inside the async thread, since there is no current UI set for that thread (internally it is stored via ThreadLocals, thus those are null at this point).

The easiest way is to set the current UI to a variable temporary outside of the thread. The async thread can then access it, for instance:


var ui = UI.getCurrent();

// start your thread, e.g. with some executor service
service.execute(() -> {
    // ... your longterm background operation ...
    
    // when its done
    ui.access(() -> /* your ui code */);
});

The only challenge here is, that you have to assure, that all the components updated by these listeners are attached to this UI.

Why that?

Let’s say, your user has two browser tabs open, and both UIs are accessed in the same async thread. This might lead to the problem, that you will access UI / Browser tab 1, but inside the listener you also update components of UI / Browser tab 2. This can lead to issues, for instance when the UI 2 components also call UI.getCurrent() (which then returns UI 1 - a bit complicated and hard to debug, when issues appear :D )

If that is the case, you should delegate the ui access into your listeners and obtain the respective UIs there in some way (for instance by using Component#getUI(), which returns the UI the component is attached to or empty, if not).

For instance something like


// start your thread, e.g. with some executor service
service.execute(() -> {
    // ... your longterm background operation ...
    
    // call the listeners
});

var comboBox = ...;

// registering your listener to the REST service to be informed, when the backend is updated
myRestManager.addListener(() -> {
    comboBox.getUI().ifPresent(ui -> ui.access(() -> /* update combobox */));
});

A bit more complicated and needs discipline, that all listeners do their correct part, but this way you always use the respective combobox’s UI and the update code is only executed, when the combobox is attached and has a UI.

1 Like

Thank you for the detailed post! That all makes sense, I will take a look at implementing your suggestion.