How can i use Guice in JavaFX controllers?
Asked Answered
P

4

7

I have a JavaFX application where I would like to introduce Guice because my Code is full of factorys right now, only for the purpose of testing.

I have one use case where i have a controller class of a certain view. This controller class has a viewmodel and I pass the model to the viewmodel via the constructor of the controller class.

In the controller class I have a contactservice object that provides the edit/save/delete operations. As of now I have an interface of that object and provide an implementation and a Mock. This object can be retrieved by a Factory.getInstance() method.

What I want to do is something like this:

public class NewContactController implements Initializable {
    // ------------------------------------------------------------------------
    // to inject by guice
    // ------------------------------------------------------------------------
    private ContactService contactService;

    @Inject
    public void setContactService(ContactService srv) {
        this.contactService = srv;
    }
    // edit window
    public NewContactController(Contact c) {
        this.viewModel = new NewContactViewModel(c);
    }
    // new window
    public NewContactController() {
        this.viewModel = new NewContactViewModel();
    }

    @FXML
    void onSave(ActionEvent event) {
        //do work like edit a contcat, 
        contactService.editContact(viewModel.getModelToSave());
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // bind to viewmodel---------------------------------------------------
    }
}

How can I achive this? Is it a good a idea to do something like that? While I was searching for a solution I found fx-guice and similar frameworks but how can i combine these two? Specially how can I let this fields be injected AND instanciate the controller myself or at least give it some constructor args?

Pollack answered 5/5, 2014 at 11:19 Comment(0)
W
18

I don't use Guice, but the simplest approach would appear to be just to use a controller factory on the FXMLLoader. You can create a controller factory that instructs the FXMLLoader to use Guice to initialize your controllers:

final Injector injector = Guice.createInjector(...);
FXMLLoader loader = new FXMLLoader(getClass().getResource(...));
loader.setControllerFactory(new Callback<Class<?>, Object>() {
   @Override
   public Object call(Class<?> type) {
       return injector.getInstance(type);
   }
});
// In Java 8, replace the above with 
// loader.setControllerFactory(injector::getInstance);
Parent root = loader.<Parent>load();
Woodpile answered 5/5, 2014 at 11:34 Comment(3)
Ok so by definition I have to let any framework to instantiate the controller? Can I pass the controller any parameters to the constructor then? Either with afterburner or Guice or lets say Spring? I mean if my Controller would inherit from a framework class like it does in Roboguice how would I need to write the code then?Pollack
Great answer, just wanted to mention this can be done as a one liner in java 8: loader.setControllerFactory(injector::getInstance);Blakemore
Just to be clear, does this need a new loader for each controller class?Monogenetic
W
2

There's a good DI framework for javaFX called afterburner.fx. Check it out, I think it's the tool you're looking for.

Wb answered 5/5, 2014 at 11:52 Comment(3)
afterburner.fx is excellent, if you are not too committed to Guice already.Woodpile
afterburner.fx's latest release is nearly 4 years old (April 2016). Granted, Guice is about 1 year since its last release, but it also has a lot more community supportMonogenetic
@Monogenetic I can't speak for Adam, creator of this library, but I assume he'll welcome any external contributionsWb
B
0

Assuming you (could) instantiate the controller by hand/guice and not from the FXML, you could use https://github.com/google/guice/wiki/AssistedInject if you need to pass any non DIable parameter to the constructor. You would then set the controller manually to the FXMLLoader with .setController(this) and load the FXML file in the constructor of the controller.

Not sure if there are any drawbacks, but this kind of system seems to work for me :)

Blowgun answered 18/11, 2015 at 8:54 Comment(0)
R
0

To use JavaFx with Guice :

  1. Extend javafx.application.Application & call launch method on that class from the main method. This is the application’s entry point.
  2. Instantiate dependency injection container of your choice. E.g. Google Guice or Weld.
  3. In application’s start method, instantiate FXMLLoader and set it’s controller factory to obtain controllers from the container. Ideally obtain the FXMLLoader from the container itself, using a provider. Then give it an .fxml file resource. This will create content of the newly created window.
  4. Give the Parent object instantiated in previous step to Stage object (usually called primaryStage) supplies as an argument to the start(Stage primaryStage) method.
  5. Display the primaryStage by calling it’s show() method.

Code Example MyApp.java

public class MyApp extends Application {
    @Override
    public void start(Stage primaryStage) throws IOException {
        Injector injector = Guice.createInjector(new GuiceModule());
        //The FXMLLoader is instantiated the way Google Guice offers - the FXMLLoader instance
        // is built in a separated Provider<FXMLLoader> called FXMLLoaderProvider.
        FXMLLoader fxmlLoader = injector.getInstance(FXMLLoader.class);

        try (InputStream fxmlInputStream = ClassLoader.getSystemResourceAsStream("fxml\\login.fxml")) {
            Parent parent = fxmlLoader.load(fxmlInputStream);
            primaryStage.setScene(new Scene(parent));
            primaryStage.setTitle("Access mini Stock App v1.1");
            primaryStage.show();
            primaryStage.setOnCloseRequest(e -> {
                System.exit(0);
            });
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {

        launch();
    }

}

Then FXMLLoaderProvider.java

public class FXMLLoaderProvider implements Provider<FXMLLoader> {
    
        @Inject Injector injector;
    
        @Override
        public FXMLLoader get() {
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(p -> {
                return injector.getInstance(p);
            });
            return loader;
        }
    
    }

Thanks to mr. Pavel Pscheidl who provided us with this smart code at Integrating JavaFX 8 with Guice

Rumsey answered 29/7, 2020 at 21:15 Comment(2)
I fear, you copied the wrong code out of pavels post. You need the code where the controller factory is set. Your example above does not add guice to fxml loaders resp. controllers.Driscoll
@PeterPaulKiefer i didnt notice that, now i edited the example by my own working code, thank you very much.Rumsey

© 2022 - 2024 — McMap. All rights reserved.