I’ve asked Claude for you because I was curious how well Claude understands Vaadin without Skills and MCP. Here’s what I got:
Good question, and the fix is mostly about where the signal lives. The signal should not be created by MainLayout. Put it in a session scoped Spring bean, then inject that bean into both the layout and the profile view.
Here is an answer you can post.
Move the signal out of the layout
A @VaadinSessionScope bean is created the first time somebody injects it. That happens when MainLayout is built, so after the login. At that moment AuthenticatedUser already works, and you can load the entity in the bean constructor.
@SpringComponent
@VaadinSessionScope
public class CurrentUser {
private final ValueSignal<UserInfo> signal;
public CurrentUser(AuthenticatedUser authenticatedUser, UserService userService) {
this.signal = new ValueSignal<>(
authenticatedUser.get()
.map(u -> userService.toUserInfo(u))
.orElse(null));
}
public Signal<UserInfo> get() {
return signal.asReadonly(); // views can read
}
public void update(UserInfo info) {
signal.set(info); // only this class can write
}
}
new ValueSignal<>() without an argument is also allowed and starts with null, so even a lazy load is no problem. You just guard for null when you read it.
Use it in MainLayout
public MainLayout(CurrentUser currentUser) {
Signal<UserInfo> user = currentUser.get();
Span name = new Span();
name.bindText(user.map(u -> u == null ? "" : u.name()));
Avatar avatar = new Avatar();
Signal.effect(avatar, () -> {
UserInfo u = user.get();
avatar.setName(u == null ? null : u.name());
avatar.setImage(u == null ? null : u.pictureUrl());
});
...
}
bindText() and friends are the preferred way. Use Signal.effect(component, ...) only for properties that have no binding method, like the avatar image. The effect is tied to the component lifecycle, so no manual cleanup.
In the profile view
saveButton.addClickListener(e -> {
UserInfo saved = userService.save(form.getValue());
currentUser.update(saved); // MainLayout updates itself
});
Three things I would add
- Do not put your JPA entity in the signal. Signals compare values with
equals() and expect immutable values. A detached entity that you mutate in place will not trigger an update. Use a small record like record UserInfo(String name, String email, String pictureUrl) {}.
- If the same user has two browser tabs open, both share the session bean, but you need
@Push so the other tab really repaints.
- If you are still on Vaadin 24.8 or 24.9, the API names are different:
com.vaadin.signals.ValueSignal with value() instead of get()/set(), and ComponentEffect.effect(...) instead of Signal.effect(...). The pattern stays the same.
There is a nearly identical example in the docs under Read-Only Signal Views, which is a good link to give the person.