Blog
FXML and Google Guice
JavaFX comes with the possibility to define your UI using a declarative XML-Syntax instead of writing Java code. To react on user input the XML-Syntax allows you to call back into programm code. A simple example looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!--
Do not edit this file it is generated by e(fx)clipse from /at/bestsolution/efxclipse/runtime/examples/guice/guice.fxgraph
-->
<?import java.lang.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns:fx="http://javafx.com/fxml" fx:controller="at...LoginController">
<children>
<Label layoutX="10" layoutY="10">
<text>Username</text>
</Label>
<TextField fx:id="username" layoutX="80" layoutY="10"/>
<Label layoutX="10" layoutY="40">
<text>Password</text>
</Label>
<PasswordField fx:id="password" layoutX="80" layoutY="40"/>
<Button layoutX="80" layoutY="70" onAction="#login">
<text>Login</text>
</Button>
<Label fx:id="message" layoutX="80" layoutY="100"/>
</children>
</AnchorPane>
The controller:
package at.bestsolution.efxclipse.runtime.examples.guice.controller;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import at.bestsolution.efxclipse.runtime.examples.guice.service.ILoginService;
import com.google.inject.Inject;
public class LoginController {
@FXML
TextField username;
@FXML
PasswordField password;
@FXML
Label message;
@FXML
public void login(ActionEvent event) {
// do the login
}
}
And load the FXML-File like this:
FXMLLoader.load(
GuiceExample.class.getResource("guice.fxml"),
null,
new JavaFXBuilderFactory()
);
If you are used to dependency injection - like I am since working on Eclipse 4 - you can't imagine to work without it anymore and so you'd like to make guice inject an ILoginService to the controller.
package at.bestsolution.efxclipse.runtime.examples.guice.controller;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import at.bestsolution.efxclipse.runtime.examples.guice.service.ILoginService;
import com.google.inject.Inject;
public class LoginController {
@Inject
ILoginService loginService;
@FXML
TextField username;
@FXML
PasswordField password;
@FXML
Label message;
@FXML
public void login(ActionEvent event) {
try {
long id = loginService.login(username.getText(), password.getText());
message.setStyle("-fx-text-fill: green;");
message.setText("Logged in as User: " + id);
} catch (IllegalArgumentException e) {
message.setStyle("-fx-text-fill: red;");
message.setText(e.getMessage());
}
}
}
Now the question is how to make the FXMLLoader use Guice to inject stuff. The problem is that you can't pass the loader a delegate so that you can make Guice create the controller instance which means you can't use constructor injection but that's a minor problem for me. So we let the FXMLLoader construct the controller and execute the injection afterwards:
public static <N> N loadFXML(Injector injector, URL url) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(url);
loader.setBuilderFactory(new JavaFXBuilderFactory());
InputStream in = url.openStream();
N value = (N) loader.load(in);
in.close();
if( loader.getController() != null ) {
injector.injectMembers(loader.getController());
}
injectLoaders(loader, injector);
return value;
}
private static void injectLoaders(FXMLLoader parentLoader, Injector injector) {
for( FXMLLoader l : parentLoader.getIncludes() ) {
if( l.getController() != null ) {
injector.injectMembers(l.getController());
injectLoaders(l, injector);
}
}
}
I've packaged up the code in my runtime modules so you can make use of it quite easy. A similar implementation is available for Eclipse-DI but Eclipse-DI requires an OSGi-Runtime whereas the guice one can be used with or without OSGi.