Skip to content

Dependency Injection

Manuel Mauky edited this page Jun 20, 2016 · 12 revisions

What is Dependency Injection?

Dependency Injection is a design pattern that answers the question of how a class get references to instances of other classes it depends on. Instead of creating the instances by yourself (with the new operator) or getting them from a factory or service locator, the references are passed into the class from outside, for example as a constructor parameter.

You can implement this design pattern by hand but there are also many frameworks available like CDI, Guice, EasyDI, Spring Framework and EJB.

MvvmFX supports CDI and Guice as dependency injection frameworks out of the box. Other frameworks can be plugged into mvvmFX but it's also possible to use mvvmFX without dependency injection at all.

CDI/Weld Support

There is an mvvmFX extension for CDI available. To add CDI support you need to add the maven dependency:

<dependency>
    <groupId>de.saxsys</groupId>
    <artifactId>mvvmfx-cdi</artifactId>
    <version>${mvvmfx-version}</version>
</dependency>

This uses JBoss Weld as CDI implementation.

Your application class should extend from de.saxsys.mvvmfx.cdi.MvvmfxCdiApplication.

Example:

public class Starter extends MvvmfxCdiApplication{

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

    @Override
    public void startMvvmfx(Stage stage){
       // your code to initialize the view.
    }
}

Another example can be seen at welcome-example.

Guice Support

There is an mvvmFX extension for Guice available. To add Guice support you need to add the maven dependency:

<dependency>
    <groupId>de.saxsys</groupId>
    <artifactId>mvvmfx-guice</artifactId>
    <version>${mvvmfx-version}</version>
</dependency>

This extension is based on fx-guice.

Your application class should extend from de.saxsys.mvvmfx.guice.MvvmfxGuiceApplication.

Example:

public class Starter extends MvvmfxGuiceApplication {

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

    @Override
    public void startMvvmfx(final Stage stage) throws Exception {
        // your code to initialize the view
    }
}

Another example can be seen at welcome-example.

Livecycle (@PostConstruct)

Both CDI and Guice are supporting field injection which means that dependencies are directly injected into fields of the class. In the following example the annotation @Inject is placed above a field of the class. A DI container that supports field injection will inject the instance of MyService directly into this field even though it is marked as private. One thing to keep in mind when using field injection is that you can't use the constructor for initialization logic because at the point in time when the constructor is invoked the field will not be injected yet:

public void MyViewModel implements ViewModel {
    @Inject
    private MyService service;
    ...

    public MyViewModel() {
        service.someMethod();  // throws NullPointerException
    }
}

For this reason most DI frameworks provides some sort of live cylce methods that can be used for initialization logic. Both Guice and CDI support the annotation @PostConstruct like this:

public void MyViewModel implements ViewModel {
    @Inject
    private MyService service;
    ...
    @PostConstruct
    public void postconstruct() {
        service.someMethod();  // OK
    }
}

The DI container will invoke the method that is annotated with @PostConstruct after all dependencies that are managed by the DI container are injected.

However, this will not work for dependencies that are managed by mvvmFX framework, like @InjectScope and @InjectRessourceBundle. For the technical reasons behind this see issue #403.

To initialize dependencies from mvvmFX you should create a method with the signature public void initialize(). This is a naming convention that already exists for JavaFX controllers.

See this example:

public void MyViewModel implements ViewModel {
    @Inject
    private MyService service;

    @InjectScope
    private MyScope scope;
    ...
    @PostConstruct
    public void postconstruct() {
        service.someMethod();  // OK
        scope.someMethod(); // throws NullPointerException
    }

    public void initialize() {
        service.someMethod(); // OK
        scope.someMethod(); // OK
    }
}

Normally it's not needed to use @PostConstruct anymore in ViewModels because you can do initialization in the initialize method instead. While it's still possible to use @PostConstruct you can't access dependencies that are injected by mvvmFX in this method.

Please note: You should not use the @PostConstruct annotation on the public void initialize() method. In this case the method would be called twice. If you use @PostConstruct, don't name the method initialize().

Other Dependency Injection Frameworks

To plug in other dependency injection frameworks you have to use the MvvmFX.setCustomDependencyInjector method to set a callback of type javafx.util.Callback<Class<?>,Object>.

This callback gets a class type as parameter and has to return an instance of the given type. Your implementation of this callback should return an instance that is created and managed by your dependency injection framework. This callback will be called when a fxml file is loaded to get an instance of the specified Code-Behind class (specified by the controller attribute).

The integration of DI frameworks can be seen in the books-example where the the DI library EasyDI is used. Other examples for EasyDI can be found here and here.

No Dependency Injection

When no dependency injection framework is used nothing has to be done. In this case the code behind classes are instantiated by the FXMLLoader.

What is the reason that mvvmFX needs special handling of dependency injection frameworks?

MvvmFX is based on the usage of FXML as view description technology. In the fxml file you specify the "Code behind" part of the View with the controller attribute. By default this controller classes are instantiated by javafx's FXMLLoader to be able to inject view controls. To combine this with your dependency injection framework you need to tell the FXMLLoader where it can get controller instances. Internally this is done by FXMLLoader's setControllerFactory method which is encapsulated by mvvmFX.

Do I need Dependency Injection to use mvvmFX?

No, while dependency injection has many advantages you aren't forced to use it. MvvmFX can be used without dependency injection frameworks.

Injecting ViewModels with @InjectViewModel vs. @Inject

There is a special annotation @InjectViewModel that is used to inject the ViewModel into the View. When using CDI or Guice the developer could be tempted to use @Inject instead. This is wrong. Always use @InjectViewModel to inject the ViewModel into the View, not @Inject. The reason is that internally we are doing some special checks and logic before we inject the ViewModel into the View that can't be done when @Inject is used:

  • Check for the correct type of the ViewModel for a View. If the Type doesn't match a meaningful exception will be thrown.
  • ResourceBundles are only injected into the ViewModel when it is itself injected via @InjectViewModel.
  • When loading a View via FluentViewLoader an existing ViewModel instance can be provided (for example FluentViewLoader.fxmlView(MyView.class).viewModel(myViewModelInstance).load()). This existing ViewModel instance can only be injected via @InjectViewModel.

Even when using @InjectViewModel the ViewModel instance is still managed by the DI framework as we are internally taking the instance from the DI. This means that inside of the ViewModel you can inject other dependencies via @Inject like with any other class.

Another aspect that should be kept in mind is lifecycle methods: When using @InjectViewModel, the ViewModel instance will not be available in a post-construct method (annotated with @PostConstruct). Instead you should do your initialization logic in the initialize method that is suggested by standard JavaFX behaviour.