How to inject services into JavaFX controllers using Dagger 2 - java

JavaFX itself has some means of DI to allow binding between XML-described UIs and controllers:
<Pane fx:controller="foo.bar.MyController">
<children>
<Label fx:id="myLabel" furtherAttribute="..." />
</children>
</Pane>
The Java-side looks like this:
public class MyController implements Initializable {
#FXML private Label myLabel;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// FXML-fields have been injected at this point of time:
myLabel.setText("Hello world!");
}
}
For this to work, I can not just create an instance of MyController. Instead I have to ask JavaFX to do stuff for me:
FXMLLoader loader = new FXMLLoader(MyApp.class.getResource("/fxml/myFxmlFile.fxml"), rb);
loader.load();
MyController ctrl = (MyController) loader.getController();
So far, so good
However, if I want to use Dagger 2 to inject some non-FXML-dependencies into the constructor of this controller class, I have a problem, as I have no control over the instantiation process, if I use JavaFX.
public class MyController implements Initializable {
#FXML private Label myLabel;
/*
How do I make this work?
private final SomeService myService;
#Inject
public MyController(SomeService myService) {
this.myService = myService;
}
*/
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// FXML-fields have been injected at this point of time:
myLabel.setText("Hello world!");
}
}
There is one API that looks promising: loader.setControllerFactory(...); Maybe this is a good point to start with. But I do not have enough experience with these libraries to know how to approach this problem.

A custom ControllerFactory would need to construct Controllers of certain types only known at runtime. This could look like the following:
T t = clazz.newInstance();
injector.inject(t);
return t;
This is perfectly ok for most other DI libraries like Guice, as they just have to look up dependencies for the type of t in their dependency graph.
Dagger 2 resolves dependencies during compile time. Its biggest features is at the same time its biggest problem: If a type is only known at runtime the compiler can not distinguish invocations of inject(t). It could be inject(Foo foo) or inject(Bar bar).
(Also this wouldn't work with final fields, as newInstance() invokes the default-constructor).
Ok no generic types. Lets look at a second approach: Get the controller instance from Dagger first and pass it to the FXMLLoader afterwards.
I used the CoffeeShop example from Dagger and modified it to construct JavaFX controllers:
#Singleton
#Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
Provider<CoffeeMakerController> coffeeMakerController();
}
If I get a CoffeeMakerController, all its fields are already injected, so I can easily use it in setController(...):
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
CoffeeMakerController ctrl = coffeeShop.coffeeMakerController().get();
/* ... */
FXMLLoader loader = new FXMLLoader(fxmlUrl, rb);
loader.setController(ctrl);
Parent root = loader.load();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.show();
My FXML file must not contain a fx:controller attribute, as the loader would try to construct a controller, which of course stands in conflict with our Dagger-provided one.
The full example is available on GitHub

Thanks to Map multibinding mechanism hint from #Sebastian_S I've managed to make automatic controller binding using Map<Class<?>, Provider<Object>> that maps each controller to its class.
In Module collect all controllers into Map named "Controllers" with corresponding Class keys
#Module
public class MyModule {
// ********************** CONTROLLERS **********************
#Provides
#IntoMap
#Named("Controllers")
#ClassKey(FirstController.class)
static Object provideFirstController(DepA depA, DepB depB) {
return new FirstController(depA, depB);
}
#Provides
#IntoMap
#Named("Controllers")
#ClassKey(SecondController.class)
static Object provideSecondController(DepA depA, DepC depC) {
return new SecondController(depA, depC);
}
}
Then in Component, we can get an instance of this Map using its name. The value type of this map should be Provider<Object> because we want to get a new instance of a controller each time FXMLLoader needs it.
#Singleton
#Component(modules = MyModule.class)
public interface MyDiContainer {
// ********************** CONTROLLERS **********************
#Named("Controllers")
Map<Class<?>, Provider<Object>> getControllers();
}
And finally, in your FXML loading code, you should set new ControllerFactory
MyDiContainer myDiContainer = DaggerMyDiContainer.create()
Map<Class<?>, Provider<Object>> controllers = myDiContainer.getControllers();
FXMLLoader loader = new FXMLLoader();
loader.setControllerFactory(type -> controllers.get(type).get());

Alternatively you can do something like:
...
loader.setControllerFactory(new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> type) {
switch (type.getSimpleName()) {
case "LoginController":
return loginController;
case "MainController":
return mainController;
default:
return null;
}
}
});
...
As #Sebastian_S noted, a reflection-based controller factory is not possible. However calling setController is not the only way, I actually like this setControllerFactory approach better because it doesn't break the tooling (e.g. IntelliJ's XML inspections) but having to explicitly list out all the classes is definitely a drawback.

This is solved probably long ago for many people. I did not like the solution described here in as it relies on class names or reflection over clean design. I wrote a bit different one that looks more maintainable to my eyes.
The gist of it is to use Dagger to create the Scene that is injected into the Stage. Here is my Application class
CameraRemote context;
public static void main(String[] args) {
launch(args);
}
public SimpleUI() {
context = DaggerCameraRemote.builder().build();
}
#Override
public void start(Stage stage) throws IOException {
stage.setTitle("Remote Control");
stage.setScene(context.mainFrame());
stage.show();
}
I have in my Dagger 2 module the logic for loading fxml and customization of the controller i.e. injecting the SsdpClient
#Provides
public static Scene provideMainScene(SsdpClient ssdpClient) {
try {
FXMLLoader loader = new FXMLLoader(CameraModule.class.getResource("/MainFrame.fxml"));
Parent root;
root = loader.load();
MainController controller = (MainController) loader.getController();
controller.setClient(ssdpClient);
return new Scene(root, 800, 450);
} catch (IOException e) {
throw new RuntimeException("Cannot load MainFrame.fxml", e);
}
}
I can split further the creation of Parent instance. It is not used anywhere else and I compromised.

Related

Managing scene switching in JavaFX via FXML (Performance Question)

I'm playing arround with JavaFX and various scenes that are loaded by FXML. So I got the idea to write a manager that handles scene switching.
So far everything works, but I'm unsure if this is a good implementation.
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class SceneManager {
private static final String[] fxmlFiles = {"../gui/MainWindow.fxml", "../gui/NewGameWindow.fxml"};
private static SceneManager instance = null;
private static Stage rootStage = null;
private FXMLLoader[] loadedFxml;
private Pane[] loadedPanes;
private Scene[] scenes;
public enum States {
MAIN_MENU, NEW_GAME;
}
private SceneManager() {
try {
this.loadedFxml = new FXMLLoader[States.values().length];
this.loadedPanes = new Pane[States.values().length];
this.scenes = new Scene[States.values().length];
for(int i = 0; i < fxmlFiles.length; i++) {
loadedFxml[i] = new FXMLLoader(getClass().getResource(fxmlFiles[i]));
loadedPanes[i] = loadedFxml[i].load();
scenes[i] = new Scene(loadedPanes[i]);
}
rootStage.setScene(scenes[0]);
rootStage.setResizable(false);
rootStage.show();
} catch(IOException e) {
e.printStackTrace();
}
}
public static SceneManager getInstance() {
if(instance == null) {
instance = new SceneManager();
}
return instance;
}
public static void setUp(Stage stage) {
SceneManager.rootStage = stage;
}
public void switchScene(States state) {
rootStage.setScene(scenes[state.ordinal()]);
}
}
So what I plan to do is, load the FXML via the loader, assign it to a pane, create all scenes.
Then I set a scene as its starting scene and do the rest via the getInstance().switchScene() method in the controller.
It works well but I'm unsure if this is a good approach.
The implementation is imho pretty bad for several reasons:
The singleton pattern is not properly implemented
The singleton pattern is used to access an instance containing the relevant data/functionality via a static method, but this instance should contain all the relevant data as instance fields.
rootStage and setUp are static though.
You store data in fields that is never needed again
You never read from loadedFxml or loadedPanes outside of the loop. You could instead create local variables in the loop body.
Everything is loaded at once
For several small scenes this may not make much difference, but as you add more and more scenes this will increase startup time. Consider lazily loading the scenes.
Data for scenes is kept in different data structures
Not much of an issue, but it makes the class a bit harder to maintain. The enum stores one part of the data used to create/access the scenes fxmlFiles contains the other half. Every time you add/remove a scene you need to update both parts of your code. In cases like this it would be preferable to store the url data in the enum itself:
public enum States {
MAIN_MENU("../gui/MainWindow.fxml"), NEW_GAME("../gui/NewGameWindow.fxml");
private final url;
States(String url) {
this.url = url;
}
}
for(States state : States.values()) {
FXMLLoader loader = new FXMLLoader(getClass().getResource(state.url));
...
}
Note that you use .. in your urls here which ceases to work, if you package your program as a jar.
But using a enum in the first place is a questionable decision. This way you won't be able to add/remove scenes without recompiling.
Using static doesn't seem necessary at all
It's good to avoid the use of static at all if possible. (see Why are static variables considered evil?).
If my assumption that you only use the SceneManager class from scenes loaded by it (and for displaying the initial scene) is correct, it's not hard to pass the SceneManager instance to the controllers of the scenes to avoid the need of using SceneManager.getInstance in those classes (see Passing Parameters JavaFX FXML):
superclass for controllers
public class BaseController {
protected SceneManager sceneManager;
void setSceneManager(SceneManager sceneManager) { // if SceneManager and BaseController are in different packages, change visibility
this.sceneManager = sceneManager;
}
}
FXMLLoader loader = ...
Pane pane = loader.load();
BaseController controller = loader.getController();
controller.setSceneManager(this);
Using the url as identifiers for the scenes for simplicity, you could improve the implementation:
public class SceneManager {
private final Stage rootStage;
public SceneManager(Stage rootStage) {
if (rootStage == null) {
throw new IllegalArgumentException();
}
this.rootStage = rootStage;
}
private final Map<String, Scene> scenes = new HashMap<>();
public void switchScene(String url) {
Scene scene = scenes.computeIfAbsent(url, u -> {
FXMLLoader loader = new FXMLLoader(getClass().getResource(u));
try {
Pane p = loader.load();
BaseController controller = loader.getController();
controller.setSceneManager(this);
return new Scene(p);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
});
rootStage.setScene(scene);
}
}
This allows you to
create different managers for different stages
load scenes when they are needed first
dynamically add more scenes
prevents state where switchScene is called but the stage is null
simplifies access to the SceneManager in controller classes to sceneManager.switchScene
SceneManager is possibly available for garbage collection before the program completes since there is no static reference to it.

How to make Combobox retain its selected value in JavaFx while calling from another class? [duplicate]

I would like to communicate with a FXML controller class at any time, to update information on the screen from the main application or other stages.
Is this possible? I havent found any way to do it.
Static functions could be a way, but they don't have access to the form's controls.
Any ideas?
You can get the controller from the FXMLLoader
FXMLLoader fxmlLoader = new FXMLLoader();
Pane p = fxmlLoader.load(getClass().getResource("foo.fxml").openStream());
FooController fooController = (FooController) fxmlLoader.getController();
store it in your main stage and provide getFooController() getter method.
From other classes or stages, every time when you need to refresh the loaded "foo.fxml" page, ask it from its controller:
getFooController().updatePage(strData);
updatePage() can be something like:
// ...
#FXML private Label lblData;
// ...
public void updatePage(String data){
lblData.setText(data);
}
// ...
in the FooController class.
This way other page users do not bother about page's internal structure like what and where Label lblData is.
Also look the https://stackoverflow.com/a/10718683/682495. In JavaFX 2.2 FXMLLoader is improved.
Just to help clarify the accepted answer and maybe save a bit of time for others that are new to JavaFX:
For a JavaFX FXML Application, NetBeans will auto-generate your start method in the main class as follows:
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Now, all we need to do to have access to the controller class is to change the FXMLLoader load() method from the static implementation to an instantiated implementation and then we can use the instance's method to get the controller, like this:
//Static global variable for the controller (where MyController is the name of your controller class
static MyController myControllerHandle;
#Override
public void start(Stage stage) throws Exception {
//Set up instance instead of using static load() method
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
//Now we have access to getController() through the instance... don't forget the type cast
myControllerHandle = (MyController)loader.getController();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Another solution is to set the controller from your controller class, like so...
public class Controller implements javafx.fxml.Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
// Implementing the Initializable interface means that this method
// will be called when the controller instance is created
App.setController(this);
}
}
This is the solution I prefer to use since the code is somewhat messy to create a fully functional FXMLLoader instance which properly handles local resources etc
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/sample.fxml"));
}
versus
#Override
public void start(Stage stage) throws Exception {
URL location = getClass().getResource("/sample.fxml");
FXMLLoader loader = createFXMLLoader(location);
Parent root = loader.load(location.openStream());
}
public FXMLLoader createFXMLLoader(URL location) {
return new FXMLLoader(location, null, new JavaFXBuilderFactory(), null, Charset.forName(FXMLLoader.DEFAULT_CHARSET_NAME));
}
On the object's loading from the Main screen, one way to pass data that I have found and works is to use lookup and then set the data inside an invisible label that I can retrieve later from the controller class. Like this:
Parent root = FXMLLoader.load(me.getClass().getResource("Form.fxml"));
Label lblData = (Label) root.lookup("#lblData");
if (lblData!=null) lblData.setText(strData);
This works, but there must be a better way.

call methods inside controller from a different class javafx [duplicate]

I would like to communicate with a FXML controller class at any time, to update information on the screen from the main application or other stages.
Is this possible? I havent found any way to do it.
Static functions could be a way, but they don't have access to the form's controls.
Any ideas?
You can get the controller from the FXMLLoader
FXMLLoader fxmlLoader = new FXMLLoader();
Pane p = fxmlLoader.load(getClass().getResource("foo.fxml").openStream());
FooController fooController = (FooController) fxmlLoader.getController();
store it in your main stage and provide getFooController() getter method.
From other classes or stages, every time when you need to refresh the loaded "foo.fxml" page, ask it from its controller:
getFooController().updatePage(strData);
updatePage() can be something like:
// ...
#FXML private Label lblData;
// ...
public void updatePage(String data){
lblData.setText(data);
}
// ...
in the FooController class.
This way other page users do not bother about page's internal structure like what and where Label lblData is.
Also look the https://stackoverflow.com/a/10718683/682495. In JavaFX 2.2 FXMLLoader is improved.
Just to help clarify the accepted answer and maybe save a bit of time for others that are new to JavaFX:
For a JavaFX FXML Application, NetBeans will auto-generate your start method in the main class as follows:
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Now, all we need to do to have access to the controller class is to change the FXMLLoader load() method from the static implementation to an instantiated implementation and then we can use the instance's method to get the controller, like this:
//Static global variable for the controller (where MyController is the name of your controller class
static MyController myControllerHandle;
#Override
public void start(Stage stage) throws Exception {
//Set up instance instead of using static load() method
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
//Now we have access to getController() through the instance... don't forget the type cast
myControllerHandle = (MyController)loader.getController();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Another solution is to set the controller from your controller class, like so...
public class Controller implements javafx.fxml.Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
// Implementing the Initializable interface means that this method
// will be called when the controller instance is created
App.setController(this);
}
}
This is the solution I prefer to use since the code is somewhat messy to create a fully functional FXMLLoader instance which properly handles local resources etc
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/sample.fxml"));
}
versus
#Override
public void start(Stage stage) throws Exception {
URL location = getClass().getResource("/sample.fxml");
FXMLLoader loader = createFXMLLoader(location);
Parent root = loader.load(location.openStream());
}
public FXMLLoader createFXMLLoader(URL location) {
return new FXMLLoader(location, null, new JavaFXBuilderFactory(), null, Charset.forName(FXMLLoader.DEFAULT_CHARSET_NAME));
}
On the object's loading from the Main screen, one way to pass data that I have found and works is to use lookup and then set the data inside an invisible label that I can retrieve later from the controller class. Like this:
Parent root = FXMLLoader.load(me.getClass().getResource("Form.fxml"));
Label lblData = (Label) root.lookup("#lblData");
if (lblData!=null) lblData.setText(strData);
This works, but there must be a better way.

How to wire multiple fxml controllers using spring dependency-injection?

I read few spring tutorials with basic examples, and I am a bit confused on how to wire up things properly.
The trouble is, I wanted to use application context to pull singleton controller references, but I read on few other topics that application context should not be accessed directly unless it is absolutely necessary. I think I should use constructor to instantiate reference I want, but here things get all blurry for me.
I have javafx application with several fxml files. I have one main fxml and other are loaded dynamically inside main.
I'll use simplified code for example with two fxml controllers, MainController.java (for main fxml) and ContentController.java (for content fxml)
The idea is that content fxml has TabPane, and main fxml has button which opens new tab in TabPane on ContentController.
I am currently doing something like this
bean xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="contentController"
class="ContentController"
scope="singleton" />
</beans>
MainControler:
public class MainOverlayControler {
ApplicationContext context;
#FXML
private BorderPane borderPane;
#FXML
private void initialize() {
loadContentHolder();
}
#FXML
private Button btn;
#FXML
private void btnOnAction(ActionEvent evt) {
((ContentController)context.getBean("contentController")).openNewContent();
}
private void loadContentHolder() {
//set app context
context = new ClassPathXmlApplicationContext("Beans.xml");
Node fxmlNode;
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setController(context.getBean("contentController"));
try {
fxmlNode = (Node)fxmlLoader.load(getClass().getResource("Content.fxml").openStream());
borderPane.setCenter(fxmlNode);
} catch (IOException e) {
e.printStackTrace();
}
}
ContentController:
public class ContentController {
#FXML
private TabPane tabPane;
public void openNewContent() {
Tab newContentTab = new Tab();
newContentTab.setText("NewTab");
tabPane.getTabs().add(newContentTab);
}
}
Main class:
public class MainFX extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
Parent root = (Parent) fxmlLoader.load(getClass().getResource("main.fxml").openStream());
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
public static void main(String[] args) {
launch(args);
}
}
Question:
I would like to know how to do same thing with constructor DI, or some other way if I misunderstood the concept.
I need to be able to call "openNewContent" on singleton instance of "ContentController" from multiple other controllers as well.
Read some more on JavaFX and Spring integration. I recommend series by Stephen Chin
Basically, You need to embed your JavaFX classes into Spring context life cycle, and it is very nicely described in linked article.
You can also check his example project on GitHub, where the code is simplified with controller factories introduced in JavaFX 2.1 (check here)

MVC Pattern in JavaFX With Scene Builder

I'm new to JavaFX and am struggling to create a proper MVC architecture given my current setup. I clicked together a UI using Scene Builder and designated a Controller class.
Startup:
public class Portal extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("PortalUI.fxml"));
stage.setTitle("Portal");
stage.setScene(new Scene(root));
stage.show();
}
}
And the Controller class contains the rest of the code.
public class AccommodationPortalView implements Initializable {
#Override
public void initialize(URL url, ResourceBundle resources) {
// Work here.
}
}
My professor asked that I further separate the concerns and responsibilities of this application. The Controller is not only managing state and talking with the backend, but also updating the View.
My first response was to let the Controller class become the View and create two other classes for the Controller and Model.
However, I'm at a loss at how to connect these pieces. I never need to instantiate the View, so there is no View instance that I can pass to my Controller, for example. Next, I tried making them all singletons and simply letting Controller fetch them at runtime, but that gives me an error.
public class Portal extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("PortalUI.fxml"));
stage.setTitle("Portal");
stage.setScene(new Scene(root));
stage.show();
// Controller gets a View and Model instance in initialize();
// Error: Instantiation and Runtime Exception...
PortalController.INSTANCE.initialize();
}
}
How do I properly set-up an MVC pattern using my current configuration? Is a different architecture required?
Your,
-- View is a primary Stage provided by the JavaFX platform at start up. This stage has the only Scene (you have created and set) which in turn has a parent node content root (your variable). This root node is set by FXMLLoader and represents the layout/node structure defined in the "PortalUI.fxml" file.
In other words Stage -> Scene -> PortalUI.fxml(root) will define the view part.
-- Controller is the class that implements Initializable and that you specified in your PortalUI.fxml file with fx:controller=" " attribute. The class you have specified there (PortalController I suppose) will be created and invoked its initialize() method by the FXMLLoader. Namely the Controller will be created when the PortalUI.fxml file is loaded, so you don't need to create and initialize it yourself. To get the created/initialized instance of the controller from the FXMLLoader look the Accessing FXML controller class.
-- Model is the underlying data structure stored and managed by the controller. It can be anything representing the "data". For example, Person, PortalInfo etc. classes.

Categories