JavaFX + CDI (Weld)

Put in the class path:

The challenge is to bring FXMLLoader and CDI together, because JavaFX is creating naked objects by itself, now they have to be „CDI-aware“.

package tutego.fx;

import java.nio.charset.*;
import javafx.fxml.FXMLLoader;
import javafx.util.Callback;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;

public class FXMLLoaderProducer
{
  @Inject
  Instance<Object> instance;

  @Produces
  public FXMLLoader createLoader()
  {
    return new FXMLLoader( null, null, null, new Callback<Class<?>, Object>() {
      @Override public Object call( Class<?> param ) {
        return instance.select( param ).get();
      }
    }, StandardCharsets.UTF_8 );
  }
}

That was the hardest part.

The first regular class has the unique main(String[]) method and it’s a JavaFX application. It starts Weld, the CDI container.

package tutego.fx;

import java.io.IOException;
import javafx.application.Application;
import javafx.stage.Stage;
import org.jboss.weld.environment.se.*;

public class Main extends Application
{
  private Weld weld;

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

  @Override
  public void init()
  {
    weld = new Weld();
  }

  @Override
  public void start( Stage stage ) throws IOException
  {
    weld.initialize().instance().select( FxMain.class ).get().start( stage, getParameters() );
  }

  @Override
  public void stop()
  {
    weld.shutdown();
  }
}

Weld delegates to the FxMain class, the first CDI-enabled class:

package tutego.fx;

import java.io.*;
import javafx.application.Application.Parameters;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.stage.Stage;
import javax.inject.Inject;

public class FxMain
{
  @Inject
  private FXMLLoader fxmlLoader;

  public void start( Stage stage, Parameters parameters ) throws IOException
  {
    try ( InputStream fxml = RandomController.class.getResourceAsStream( "/random.fxml" ) ) {
      Parent root = (Parent) fxmlLoader.load( fxml );
      stage.setScene( new Scene( root ) );
      stage.show();
    }
  }
}

The injected FXMLLoader now has to load the FXML-file random.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="tutego.fx.RandomController">
  <children>
    <Button onAction="#onButtonClick" text="Next random" />
    <Label id="text" fx:id="label" />
  </children>
</VBox>

In the FXML-file there is a reference to the FX Controller. JavaFX has to load it and can make the injections with the help of our very first class. A regular service is getting injected into the controller:

package tutego.fx;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javax.inject.Inject;

public class RandomController
{
  @FXML
  private Label label;

  @Inject
  private RandomService randomService;

  @FXML
  public void onButtonClick()
  {
    label.setText( "Random " + randomService.nextInt() );
  }
}

The service itself is a simple singleton:

package tutego.fx;

import java.util.Random;
import javax.inject.Singleton;

@Singleton
public class RandomService
{
  private Random rnd = new Random();

  public int nextInt()
  {
    return rnd.nextInt();
  }
}

Thats it!

PS: When you start, dont forget to put a (even blank) beans.xml in META-INF.