I tried to implement a controller with the singleton pattern like described a couple of times on the web.
However I cant get the application to run because of the following exception.
java.lang.IllegalAccessException: Class sun.reflect.misc.ReflectUtil can not access a member of class testapp.Controller with modifiers "private"
I guess thats because the constructor is declared private. I dont see what I am doing wrong at this point.
In case I wasn't clear what my problem is I will describe the use case of what I am going to do.
Inside the start(Stage stage)
function afaik is the only place the onclose event can be defined(please correct me if I am wrong). On closing the window some clean-up operations need to be exectued. These operation are inside the controller which I cant get access of in the start() function
. Therefore the idea was to build the controller as a singleton to keep one single instance alive and provide acces to the main class.
The Link Creating a Singleton Controller class in JavaFX advised by sillyfly seems not to be a possible sollution for me bacause the controller is passed to a model class not the main class. Also the model constructor is called manually which is not the case I am dealing with.
To solve your problem you have two possibilities. Either you provide an instance of your singleton controller to the FXML loader which you have instantiated yourself
https://docs.oracle.com/javase/8/javafx/api/javafx/fxml/FXMLLoader.html#setController-java.lang.Object-
or you provide the FXML loader with a controller factory which knows how to instantiate your controller.
https://docs.oracle.com/javase/8/javafx/api/javafx/fxml/FXMLLoader.html#setControllerFactory-javafx.util.Callback-
This just reads like an X-Y problem to me.
It makes no sense (to me) to make a controller a singleton. In practice, every JavaFX controller is stateful and needs some access to the view state: at some point you are going to want to check the text that is in a text field, or the selected item in a combo box, etc, so you need to have references to the elements of the view (the text field or combo box, etc). Because of this, in practice, you need to have a 1-1 correspondence between controller instances and instances of the hierarchy defined by the FXML file. So if you make the controller a singleton, then you can only ever load the FXML file once.
The problem is that there is no way to enforce (at the same level of enforcement as a singleton works) "only load this FXML file once". Thus you have to enforce this rule solely by your programming logic. Of course, once you do this, your programming logic now enforces that you only have one controller instance anyway, so you effectively achieve the same thing. If you enforce this by programming logic, it allows you to reuse the controller-FXML view pair if you later want to; making the controller class a singleton prevents it, though it will not be apparent from your application code that this is the case. So making the controller a singleton reduces reuse (in a way that will not be obvious when you return to the code) while providing no additional benefit.
Your state in your question that you "need" to do this because you need to access the controller in the onClose
handler for the primary stage. I don't see why you can't just access the controller in the usual way:
@Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/fxml/file.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
MyControllerClass controller = loader.getController();
primaryStage.setOnHidden(e -> {
// do clean-up:
controller.shutdown();
// ...
});
primaryStage.show();
}
Even if you delegate loading the FXML to some other class, you must have some route defined between the start
method (with its reference to the primary stage) and the method that loads the FXML
, so you have a route through which you can pass the stage reference or pass the controller reference to achieve what you need.
In other class:
@FXML
private void btnIngresarOnAction() {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("/fxml/Frame.fxml"));
Parent rootNode = fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("Frame");
Scene scene = new Scene(rootNode);
stage.setOnCloseRequest(event -> FrameController.frame = null);
stage.setScene(scene);
stage.show();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
In class singleton:
@Lazy
@Component
public class FrameController implements Initializable {
public static FrameController frame;
public FrameController() {
if (frame == null) {
frame = this;
} else {
throw new RuntimeException("Singleton FXML");
}
}
@Override
public void initialize(URL url, ResourceBundle rb) {
System.out.println("Initialize");
}
}