79642140

Date: 2025-05-28 12:00:14
Score: 2.5
Natty:
Report link

A Clean Alternative: Mediator-Based MVVM for Wizards in JavaFX + Spring Boot

After exploring multiple architectural options and learning from excellent feedback on StackOverflow and SoftwareEngineering.SE (thanks @James_D, @DaveB, @Ewan, and others), I implemented a third solution that closely follows the Mediator Pattern, and it’s worked great in production.

Using WizardViewModel as a Mediator

Each StepViewModel is fully decoupled β€” it doesn’t call the master directly or publish Spring Events.

Instead:

Controller β†’ Mediator Communication (Example)

@Component
public class AdminPinController {

    @FXML private PasswordField user1EnterPin;

    private final WizardViewModel wizardVm;
    private final ValidationHelper validationHelper;
    private final ValidationState validationState;
    private final Validator validator = new Validator();

    public AdminPinController(WizardViewModel wizardVm,
                              ValidationHelper validationHelper,
                              ValidationState validationState) {
        this.wizardVm = wizardVm;
        this.validationHelper = validationHelper;
        this.validationState = validationState;
    }

    @FXML
    public void initialize() {
        validationHelper.registerAdminsLoginValidations(validator, user1EnterPin);
        validationState.formInvalidProperty().bind(validator.containsErrorsProperty());

        wizardVm.validationRequestedProperty().addListener((obs, oldVal, newVal) -> {
            if (Boolean.TRUE.equals(newVal) && isCurrentStepRelevant()) {
                handleValidation();
                wizardVm.validationProcessed();
            }
        });
    }

    private boolean isCurrentStepRelevant() {
        return wizardVm.getCurrentContext().getStep() == WizardStep.LOGIN_USER1;
    }

    private void handleValidation() {
        if (!validator.validate()) {
            wizardVm.setErrorMessage("PIN validation failed.");
            return;
        }
        wizardVm.onUserCompletedStep();
    }
}

Inside the Mediator: WizardViewModel


@Component
public class WizardViewModel {

    private final ObjectProperty<WizardStep> currentWorkflowStep = new SimpleObjectProperty<>(WizardStep.LOGIN_USER1);
    private final ObjectProperty<ViewKey> currentViewKey = new SimpleObjectProperty<>();

    public void onUserCompletedStep() {
        WizardStep next = currentWorkflowStep.get().next();
        currentWorkflowStep.set(next);
        currentViewKey.set(resolveViewFor(next));
    }

    public void setErrorMessage(String message) { /* ... */ }
    public void validationProcessed() { /* ... */ }
    public ReadOnlyObjectProperty<WizardStep> currentWorkflowStepProperty() { return currentWorkflowStep; }
    public ReadOnlyObjectProperty<ViewKey> currentViewKeyProperty() { return currentViewKey; }
}

Validation and Error Management


Why Not Use Spring Events or Property Binding?

Pattern Pros Cons
JavaFX Property Binding Reactive, type-safe Wizard must reference every StepViewModel
Spring Events Fully decoupled, modular Async, UI-thread issues, more boilerplate
Mediator (this) Centralized logic, sync, testable No boilerplate, Requires Controller to forward calls

πŸ“Œ Summary & Benefits

βœ… Centralized workflow logic
βœ… Fully decoupled StepViewModels
βœ… No Spring Events or property wiring overhead
βœ… MVVM-pure: Controller handles UI β†’ ViewModel handles state
βœ… Reactive, testable, and easy to debug
βœ… Works cleanly with JavaFX threading

Reasons:
  • Blacklisted phrase (0.5): thanks
  • Blacklisted phrase (1): StackOverflow
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • User mentioned (1): @James_D
  • User mentioned (0): @DaveB
  • User mentioned (0): @Ewan
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: Billie