Static instances of class Javafx

1.9k Views Asked by At

I've a main class that contains main method as well as start method. Now I've a lot of other classes also. For example I've a login class, signup class and so on. I've a class that just handles the data of currently logged on user. that class is needed to be accessed from a lot of controllers. As this user is always gonna be only one so one instance of class. What i want to know what is the best approach so every class can access it update it.

What I'm currently applying is I'm making a static instance of logged user data type class in my main class. and then accessing them.

    public static loggedUserData user  = new loggedUserData();

then accessing it from anywhere as:

    demo.user.set(...);

While class methods and fields are non static. I think there could be a better way to resolve this.

1

There are 1 best solutions below

2
On BEST ANSWER

Your user object is application data that forms part of the model in MVC/MVP etc designs:

package application;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public class DataModel {
    private ObjectProperty<User> currentUser = new SimpleObjectProperty<>();

    public final ObjectProperty<User> currentUserProperty() {
        return this.currentUser;
    }

    public final User getCurrentUser() {
        return this.currentUserProperty().get();
    }

    public final void setCurrentUser(final User currentUser) {
        this.currentUserProperty().set(currentUser);
    }



    // other properties, etc...
}

In relatively simple applications, I create a singleton controller factory that references a model instance and passes it to any controller constructor that can accept it:

package application;

import java.lang.reflect.Constructor;

import javafx.util.Callback;

public enum ControllerFactory implements Callback<Class<?>, Object> {

    INSTANCE ;

    private final DataModel model = new DataModel();

    @Override
    public Object call(Class<?> type) {
        try {
            for (Constructor<?> constructor : type.getConstructors()) {
                if (constructor.getParameterCount() == 1
                        && DataModel.class.isAssignableFrom(constructor.getParameterTypes()[0])) {
                    return constructor.newInstance(model);
                }
            }
            return type.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

(Another option, instead of using a singleton pattern here, is just to have a constructor that accepts a DataModel and be careful to always pass a reference to the same DataModel instance. Often it is quite natural to do this.)

Then you can have controllers that look like

package application;

import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class LoginController {

    private final DataModel model ;

    @FXML
    private TextField userNameTextField ;

    public LoginController(DataModel model) {
        this.model = model ;
    }

    @FXML
    private void login() {
        model.setCurrentUser(new User(userNameTextField.getText()));
    }

}

and

package application;

import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class SomeOtherController {

    private final DataModel model ;

    @FXML
    private Label userLabel ;

    public SomeOtherController(DataModel model) {
        this.model = model ;
    }

    public void initialize() {

        // Use EasyBind https://github.com/TomasMikula/EasyBind for
        // more robust binding to a "property of a property"

        userLabel.textProperty().bind(Bindings.select(model.currentUserProperty(), "userName"));
    }
}

The controllers will be automatically populated with the (shared) data model if you use the following idiom to load your FXML files:

package application;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws IOException {
        String resource = "Login.fxml" ;
        FXMLLoader loader = new FXMLLoader(getClass().getResource(resource));
        loader.setControllerFactory(ControllerFactory.INSTANCE);
        Parent root = loader.load();
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

For more complex applications (or simply if you prefer a convention-based/opinionated approach), consider using a dedicated JavaFX dependency injection framework such as Afterburner.fx.