Working since mid 90' in computer science I am currently responsible of some development teams at AGFA Healthcare. Technically I am focused on a lot of related Java technologies including but not restricted to: JEE, CDI, JavaFX, Swing. Matthieu has posted 1 posts at DZone. You can read more from them at their website. View Full User Profile

FXML & JavaFX—Fueled by CDI & JBoss Weld

08.07.2012
| 7836 views |
  • submit to reddit

It has been a while since I wanted to have CDI running with JavaFX2.
Some people already blogged on how to proceed by getting Guice injection [1] to work with JavaFX & FXML.

Well, now it's my turn to provide a way to empower JavaFX with CDI, using Weld as the implementation.

My goal was just to have CDI working, no matter how I was using JavaFX, by directly coding in plain Java or using FXML.

Ready? Let's go!!!

Bootstrap JavaFX & Weld/CDI

The launcher class will be the only place where we will have Weld-specific code—all the rest will be totally CDI compliant.

The only trick here is to make the application parameters available as a CDI-compliant object so we can reuse them afterwards.

Notice also that we use the CDI event mechanism to start up our real application code.

public class WeldJavaFXLauncher extends Application {
  /**
  * Nothing special, we just use the JavaFX Application methods to boostrap
  * JavaFX
  */
  public static void main(String[] args) {
    Application.launch(WeldJavaFXLauncher.class, args);
  }

  @SuppressWarnings("serial")
  @Override
  public void start(final Stage primaryStage) throws Exception {
    // Let's initialize CDI/Weld.
    WeldContainer weldContainer = new Weld().initialize();
    // Make the application parameters injectable with a standard CDI
    // annotation
    weldContainer.instance().select(ApplicationParametersProvider.class).get().setParameters(getParameters());
    // Now that JavaFX thread is ready
    // let's inform whoever cares using standard CDI notification mechanism:
    // CDI events
    weldContainer.event().select(Stage.class, new AnnotationLiteral<StartupScene>() {}).fire(primaryStage);
  }
}

 

Start our real JavaFX application

Here we start our real application code. We're just listening to the previously fired event (containing the Scene object to render into) so we can start showing our application.

In the following example, we load an FXML GUI, but it might have been any node created in any way.

public class LoginApplicationStarter {
  // Let's have a FXMLLoader injected automatically
 @Inject FXMLLoader fxmlLoader;

  // Our CDI entry point, we just listen to an event providing the startup scene
 public void launchJavaFXApplication(@Observes @StartupScene Stage s) {
  InputStream is = null;

  try {
   is = getClass().getResourceAsStream("login.fxml");
   // we just load our FXML form (including controler and so on)
   Parent root = (Parent) fxmlLoader.load(is);
   s.setScene(new Scene(root, 300, 275));
   s.show(); // let's show the scene
  } catch (IOException e) {
   throw new IllegalStateException("cannot load FXML login screen", e);
  } finally {
      // omitted is cleanup
  }
 }
}

 

But what about the FXML controller?

First let's have a look at the controller we want to use inside our application.

It is a pure POJO class annotated with both JavaFX & CDI annotations.

// Simple application controller that uses injected fields
// to delegate login process and to get default values from the command line using: --user=SomeUser
public class LoginController implements Initializable {
    // Standard FXML injected fields
 @FXML TextField loginField;
 @FXML PasswordField passwordField;
 @FXML Text feedback;
 
 // CDI Injected service
 @Inject LoginService loginService;
 
    // Default application parameters retrieved using CDI
 @Inject Parameters applicationParameters;
 
 @FXML protected void handleSubmitButtonAction(ActionEvent event) {
  feedback.setText(loginService.login(loginField.getText(), passwordField.getText()));
 }

 @Override
 public void initialize(URL location, ResourceBundle resources) {
  loginField.setText(applicationParameters.getNamed().get("user"));
 }
}

In order to have injection working inside the FXML controller, we need to set up JavaFX so that controller objects are created by CDI.

As we are in a CDI environment we can also have the FXMLLoader classes injected (that's exactly what we did in the previous LoginApplicationStarter class).

How can we achieve this?

We just have to provide a Producer class whose responsibility will be to create FXMLLoader instances that are able to load FXML GUIs and instantiate controllers using CDI.

The only part that's a little tricky there is that the controller instantiation depends on the required class or interface (using fx:controller in your fxml file). In order to have such a runtime injection/resolution available we use a CDI Instance Object.

public class FXMLLoaderProducer {
 @Inject Instance<Object> instance;
 
 @Produces
 public FXMLLoader createLoader() {
  FXMLLoader loader = new FXMLLoader();
  loader.setControllerFactory(new Callback<Class<?>, Object>() {
   @Override
   public Object call(Class<?> param) {
    return instance.select(param).get();
   }
  });
  return loader;
 }
}

I hope you found the article interesting and you do not hesitate to comment if you see some errors or possible enhancements.

Finally, if you are interested you can find the full source code here.

[1] http://andrewtill.blogspot.be/2012/07/creating-javafx-controllers-using-guice.htm

Published at DZone with permission of its author, Matthieu Brouillard.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Greg Brown replied on Wed, 2012/08/08 - 7:46am

Hi Matthieu,

Nice article. FYI, in JavaFX 2.2 it is possible to specify the controller externally using FXMLLoader#setController(). This eliminates the need to hard-code the controller type in your FXML document with fx:controller.

Greg

 

Matthieu Brouillard replied on Wed, 2012/08/08 - 11:14am in response to: Greg Brown

Hi Greg,

Thanks for your comment and feedback. I will look at the new introduced #setController method that can ease DI.

The solution I provided was first to show that it was possible to achieve using CDI the same than what is described in the linked article (using Guice).

Also a thing that you can notice is that in the FXML you can use an interface instead of a class so that you do not need to "hard-code" your controller type. 
For example, in order to test I have refactored the project extracting ILoginController#handleSubmitButtonAction ; added @Alternative to LoginController class ; created a SimpleLoginController that just logs when the button is clicked. Then declaring in the FXML the interface instead of the real type make the SimpleLoginController used by the GUI. Give it a try...

Matthieu

Greg Brown replied on Wed, 2012/08/08 - 11:45am in response to: Matthieu Brouillard

> Also a thing that you can notice is that in the FXML you can use an interface instead of a class so that you do not need to "hard-code" your controller type.

Yes - as the architect of FXML, I am aware of this technique.  ;-)  And you are correct that it works well for the injection scenario you describe - in fact, this is the main reason we introduced the controller factory concept in JavaFX 2.1.

 

Matthieu Brouillard replied on Wed, 2012/08/08 - 1:11pm

Sorry Greg I never had a very good memory for names and answered quickly without really taking care who I was answering.

Nevertheless, I am sure that some people do not know that interfaces can be used in FXML and then will benefit of the comment.

Keep going on with the good job on JavaFX! 

Matthieu 

Greg Brown replied on Wed, 2012/08/08 - 1:41pm in response to: Matthieu Brouillard

> Nevertheless, I am sure that some people do not know that interfaces can be used in FXML and then will benefit of the comment.

Yes, I absolutely agree. And again, nice work on the demo. It is a great example of when you might want to use a controller factory.

 

Chris Donaldson replied on Sun, 2012/11/18 - 3:42pm

 Hi Matthieu,

Great Article, however i'm new to javafx, i've downloaded your code to give it a try,
but i a getting Null pointer exceptions in the LoginController  , at line 16 and  21. The objects don't seem to get injected in the fx controller.

I'm not certain but i think i might be missing something here.
I using jdk7u7 with javafx 2.2 and maven 3.0.4.

Regards,
Chris




Matthieu Brouillard replied on Mon, 2012/11/19 - 3:18am in response to: Chris Donaldson

Hi Chris,

I just retested with the versions I published the article with (java SE 1.6, JavaFX 2.1, weld 1.1.8-final) it is working as expected.

If I find some time I'll give your versions a trial...

Matthieu

Philipp Keck replied on Tue, 2013/07/02 - 3:26am

Hi,

I really love the idea of using CDI in JavaFX and so far, your code is working pretty well. However, when I give my controllers a scope (in this case a custom per-window-scope), the producer method will (of course) start to return Weld proxies instead of plain (dependent-scope) objects. This works fine for onAction-Bindings to the controller, but it makes @FXML-injection fail. I don't fully understand, how this kind of injection works in the first place, but it obviously does not work on Weld proxies. Does anyone have an idea how to fix this?

The same problem occurs with @ApplicationScoped instead of my custom scope. Specifying a scope is necessary to cross-inject controllers and allow them to communicate.

Philipp

Matthieu Brouillard replied on Mon, 2013/07/08 - 10:29am in response to: Philipp Keck

 Hello Philipp,

I must admit that I did not played with custom-scopes. Keep me informed on any of your finding in this area, I would be very interrested in seeing a CDI app including custom events working with JavaFX.

Matthieu

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.