I’m migrating from the BeanValidationBinder to the CollaborationBinder. Everything compiles, but now I’m getting strange errors that I don’t understand and I wonder if maybe the CollaborationBinder is not compatible with records?
I know I need to do some more debugging on this one, but I decided to post this sooner rather than later to just see if there was something obviously wrong I was doing.
java.util.concurrent.ExecutionException: com.vaadin.flow.data.binder.BindingException: An exception has been thrown inside binding logic for the field element [label=‘End’]
Caused by: com.vaadin.flow.data.binder.BindingException: An exception has been thrown inside binding logic for the field element [label=‘End’]
Caused by: java.lang.NullPointerException: Cannot invoke “com.vaadin.collaborationengine.CollaborationBinder$JsonHandler.serialize(Object)” because “handler” is null
public class UpdateProjectFormBinder {
private final UpdateProjectForm form;
private final UpdateProjectService service;
private final ProjectRepository repository;
private final Account account;
private final CollaborationBinder<UpdateProjectDto> binder;
public UpdateProjectFormBinder(UpdateProjectForm form, UpdateProjectService service,
ProjectRepository repository, Account account, UserInfo userInfo) {
this.form = form;
this.service = service;
this.repository = repository;
this.account = account;
this.binder = new CollaborationBinder<>(UpdateProjectDto.class, userInfo);
}
public void addBindingAndValidation() {
binder.bindInstanceFields(form);
binder.setStatusLabel(form.errorMessageField());
binder.addValueChangeListener(_ -> form.updateButton().setEnabled(binder.hasChanges()));
form.updateButton().addClickListener(_ -> handleFormSubmission(binder));
form.resetButton().addClickListener(_ -> binder.refreshFields());
// server side validation for 'start'
LocalDateTime now = LocalDateTime.now();
LocalDateTime threeYearsLater = now.plusYears(3);
String errorMessageStart = "Start must be after %s and before %s".formatted(now, threeYearsLater);
binder.forField(form.start())
.withValidator(new DateTimeRangeValidator(errorMessageStart, now, threeYearsLater))
.bind("start");
// server side validation for 'end'
LocalDateTime start = form.start().getValue();
String errorMessageEnd = "Start must be after %s and before %s".formatted(start, threeYearsLater);
binder.forField(form.end())
.withValidator(new DateTimeRangeValidator(errorMessageEnd, start, threeYearsLater))
.bind("end");
}
private void handleFormSubmission(CollaborationBinder<UpdateProjectDto> binder) {
try {
UpdateProjectDto dto = binder.writeRecord();
RichResult<Project> result = service.with(dto, account);
result.handle(
_ -> handleUpdateSuccess(dto),
this::handleUpdateFailure
);
} catch (ValidationException e) {
form.updateButton().setEnabled(true);
} catch (IllegalArgumentException e) {
form.errorMessageField().setText(e.getMessage());
form.updateButton().setEnabled(true);
form.start().setInvalid(true);
form.end().setInvalid(true);
}
}
private void handleUpdateSuccess(UpdateProjectDto dto) {
Notification notification = Notification.show("Project updated! 🎉");
notification.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
notification.setPosition(Notification.Position.TOP_CENTER);
ProjectUpdateEvent event = new ProjectUpdateEvent(account.person().name(), dto, account.id());
String organizationId = account.organization().id();
Broadcaster.broadcast(organizationId, event);
UI.getCurrent().navigate(ProjectsView.class);
}
private void handleUpdateFailure(String error) {
String errorText = "Schedule update failed: " + error;
form.errorMessageField().setText(errorText);
form.updateButton().setEnabled(true);
}
public void populateForm(ProjectId projectId, String topic) {
UI.getCurrent().getPage().retrieveExtendedClientDetails(extendedClientDetails -> {
String timezone = extendedClientDetails.getTimeZoneId();
UpdateProjectDto dto = repository.findById(projectId)
.map(project -> UpdateProjectDto.from(project, timezone))
.orElseThrow();
binder.bindInstanceFields(form);
binder.setTopic(topic, () -> dto);
form.updateButton().setEnabled(false);
});
}
}