1

I have a singleton called MenuText. It is responsible for displaying the correct text in the menu. It gets updated dynamically.

public class MenuText implements LanguageObserver {

    private MenuText() { //I want this to be private, only one instance should exist
        Config.getInstance().subscribe(this);
    }

    private static class MenuTextHolder {
        private static final MenuText INSTANCE = new MenuText();
    }

    public static MenuText getInstance() {
        return MenuTextHolder.INSTANCE;
    }

    @Override
    public void update(Language language) {
        System.out.println("Updating...");
        switch (language) {
            case ENGLISH -> {
                setText("Play");
            }
            case GERMAN -> {
                setText("Spielen");
            }
        }
    }

    private final StringProperty text = new SimpleStringProperty("Play");

    public StringProperty textProperty() {
        return text;
    }

    public String getText() {
        return text.get();
    }

    private void setText(String text) {
        this.text.set(text);
    }
}

I have a fxml file, but the MenuText can't have a reference to it. (This would contradict the MVVM architectural style)

<?import tiles.text.MenuText?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
      prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="tiles.viewModel.GameMenuViewModel">

<!--here I want to bind to the text property-->
<Button text="???"/>

</VBox>

Initially I used <fx:define> to setup a reference to the MenuText from the fxml file, but this doesn't allow private constructors. It shouldn't be that difficult, because MenuText is static, but I'm unable to make a static reference to it's singleton.

I tried <Button text="${MenuText.getInstance().text}">


Update

As mentioned in this answer, I shouldn't use the Singleton Pattern. Based on this I added an ApplicationFactory:

//Creation of items with application lifetime
public class ApplicationFactory {

    private Config config;

    public void build() {
        config = new Config();
    }

    public Config getConfig() {
        return config;
    }
}

Is this the correct approach? I now have a MenuFactory, which gets also created in the JavaFX start() method. It sets the parent of the scene.

public class MenuFactory {
    public Parent getMenu(Config config, String fxmlLocation) {
        MenuText menuText = new MenuText(config);
        MenuViewModel menuViewModel = new MenuViewModel(config);

        try {
            FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(getClass().getResource(fxmlLocation)));
            loader.getNamespace().put("menuText", menuText);
            return loader.load();
        } catch (IOException e) {
            //...
        }
    }
}

The start() mehtod looks like this:

    @Override
    public void start(Stage primaryStage) {
        ApplicationFactory applicationFactory = new ApplicationFactory();
        applicationFactory.build();

        MenuFactory menuFactory = new MenuFactory();

        Parent root = menuFactory.getMenu(applicationFactory.getConfig(), MENU);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

This makes it way more complicated and I'm not sure if this is correct. Furthermore, I still don't know how I set the MenuText in the fxml file. I tried, but I think this isn't the correct way to set a namespace in fxml.

    <fx:define>
        <MenuText fx:id="menuText"/>
    </fx:define>

I read these documentations but don't understand how I can set this custom namespace.

Lukas
  • 397
  • 6
  • 21
  • 1
    As you are starting to implement custom factory and injection mechanisms, it might be better instead to use a library or proven solution. I'd suggest reviewing this post: [Adding Spring Dependency Injection in JavaFX](https://stackoverflow.com/questions/57887944/adding-spring-dependency-injection-in-javafx-jpa-repo-service), specifically the section on injection into FXML controllers. Also see: [mvvmFX](https://github.com/sialcasa/mvvmFX). Lighter weight dependency injection solutions are available for JavaFX (you can google them if needed), but Spring is a good option for some apps IMO. – jewelsea Aug 10 '21 at 23:58
  • 1
    Your update seems different enough from the original question (which already had an accepted answer) that it would probably have been better to ask it as a new question and reference the prior one. I'd also advise being careful of asking things like "is this the correct approach?" often the question will then be closed as based on opinion. Stack-overflow tends to favor questions that have a correct answer rather than ones which request opinions, so best to ask a question in a way that it can be answered correctly rather then by opinion. – jewelsea Aug 16 '21 at 09:46

1 Answers1

2

Be aware that the singleton pattern is widely considered to be an anti-pattern. However, if you really want to do this:

Initially I used <fx:define> to setup a reference to the MenuText from the fxml file

This is the correct approach. You can combine it with fx:factory to get a reference to an instance of a class that does not have a (public) default constructor:

<fx:define>
    <MenuText fx:factory="getInstance" fx:id="menuText" />
</fx:define>

And then do

<Button text="${menuText.text}" />

Another solution is to "manually" insert the MenuText instance into the FXML namespace:

MenuText menuText = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource(...));
loader.getNamespace().put("menuText", menuText);
Parent root = loader.load();

And then

<Button text="${menuText.text}" />

should work with no additional FXML code. This approach allows you to avoid using the singleton pattern (you're basically injecting the dependency into the FXML, so you could combine it easily with a DI framework).

James_D
  • 201,275
  • 16
  • 291
  • 322
  • I tried refactoring it with the factory pattern now. I never used the factory pattern before and ended up here: https://testing.googleblog.com/2008/08/where-have-all-singletons-gone.html I have an `ApplicationFactory` with a build() method which instantiates `Config` and then the `MenuText`. Does the `ApplicationFactory` have a field config to access it later again? Or do I have to setup all relations in the `build()` method? – Lukas Aug 10 '21 at 16:08
  • Furthermore, how can I get the MenuText in the fxml file when created in a factory? – Lukas Aug 10 '21 at 16:16
  • @Lukas `fx:factory`? – James_D Aug 10 '21 at 16:26