Kodein DI on TornadoFX

You can use Kodein as-is in your TornadoFX project, but you can level-up your game by using the libraries kodein-di-framework-tornadofx-jvm.

Kodein does work on TornadoFX as-is. The kodein-di-framework-tornadofx-jvm extensions add multiple TornadoFX-specific utilities to Kodein.
Using or not using this extension really depends on your needs.

Have a look at the TornadoFX demo project to help you going further!

Install

How to quickly get into kodein-di-framework-tornadofx-jvm:
  1. Add those lines in your dependencies block in your application build.gradle file:

    Gradle Groovy script
    implementation 'org.kodein.di:kodein-di-generic-jvm:6.5.5'
    implementation 'kodein-di-framework-tornadofx-jvm:6.5.5'
    Gradle Kotlin script
    implementation("org.kodein.di:kodein-di-generic-jvm:6.5.5")
    implementation("kodein-di-framework-tornadofx-jvm:6.5.5")
    either kodein-di-generic-jvm or kodein-di-erased must be declared in addition to the kodein-di-framework-tornadofx-jvm package.
  2. Declare your App as KodeinAware and implement a Kodein container

    Example: a TornadoFX Application declaration, KodeinAware, using TornadoFX DI as external source
    class TornadoApplication : App(MainView::class), KodeinAware { (1)
        override val kodein: Kodein get() = Kodein { (2)
            installTornadoSource() (3)
         }
    
    }
    1 Setting the App as KodeinAware will help you define a global Kodein container
    2 Define a Kodein container, accessible from almost anywhere in your application
    3 Branch Kodein upon TornadoFX dependency injection, to be able to access those dependencies through your Kodein container (see documentation)
In order to leverage the power of Kodein and the helpers we made, you should really consider making your App as KodeinAware.

Retrieving

If you defined your App as KodeinAware, you will normally be able to retrieve a Kodein container, and dependencies, from almost anywhere in your code.

You may also want to access your TornadoFX components (such as View, Fragment, Controller, etc) through a Kodein container, to help you with that check the external source section.

Closest Kodein pattern

The idea behind this concept, is to be able to retrieve a Kodein container, from an outer class. For example, making our App as KodeinAware give us the ability to have a global Kodein container that can be retrieve from multiple places, like:

  • Components (View, Fragment, Controller, etc)

  • Nodes, graphical object of the App

From a TornadoFX perspective

In TornadoFX there is no hierarchy between the different components. In fact, their is no bound between View, Fragment, Controller or ViewModel, except if you handle it yourself. However, they are available through the DI container of TornadoFX (and the one of Kodein if you use external source helper), and they all have a reference to the App.

So, if we made our App as KodeinAware, we can get the global Kodein container from any Component thanks to:

Code: Extension function on Component that get the Kodein container from the App, if its KodeinAware
fun Component.kodein() = kodein { app }

TornadoFX Component are:

  • View

  • Fragment

  • Controller

  • ViewModel

In your code you can access the global Kodein container like the following snippets.

Example: retrieving dependency through closest Kodein container from a Controller
class PersonController : Controller() {
    val repository: PersonRepository by kodein().instance() (1)
}
1 The kodein() function that will get the nearest Kodein container, from the App
Example: retrieving dependency through the closest Kodein container from a View
class PersonEditorView : View() {
    val controller: PersonController by kodein().instance() (1)
}
1 The kodein() function will get the nearest Kodein container, from the App
All TornadoFX Component can access the global Kodein container ) by calling the function kodein(), but in order to do that your App must be KodeinAware.

From a JavaFX perspective

In JavaFX, an Application is made of a Stage (the window), a Scene (the container for all content) and a graph of Node. All Node are grouped by view (in the case of TornadoFX: View or Fragment).

So, the Node hierarchy can apply to the closest Kodein pattern, limited to the scope of a View or Fragment for now. If none of the parent Node have a Kodein container, thanks to the TornadoFX facilities, we can fallback onto the global Kodein container from the App.

Example: retrieving dependency through the closest Kodein container from a Node
class EditorTabFragment : Fragment() {
    override val root = hbox {
        form {
            val author: String by kodein().instance("author") (1)
            label { author }
        }
    }
}
1 The kodein() function will get the nearest Kodein container, from the parent Node, or from the App
If none of the parent nodes have a Kodein container, we try to get to the one in the App, only if this App is KodeinAware.

Being KodeinAware

Having your classes set as KodeinAware have multiple advantages. For example this allow to cache the Kodein container or simplify the way we retrieve our dependencies.

Example: A KodeinAware TornadoFX Controller
class PersonListController : Controller(), KodeinAware { (1)
    override val kodein: Kodein = kodein() (2)

    val personEditorController: PersonEditorController by instance() (3)

    fun editPerson(person: Person) {
        personEditorController.editPerson(person) (4)
    }
    /*...*/
}
1 Set the Controller as KodeeinAware
2 Retrieve the App Kodein container from the kodein() extension function
3 Retrieve dependency using the instance() function, as the Kodein container is part of the context
4 Use the dependency
Because Kodein is lazy, the container and the dependencies will be retrieve at call site only.
To benefit from the Kodein optimization, and the facilities we provide, we highly recommend that you make your classes KodeinAware when its possible.

Extension of Kodein

This section will cover how we can extend the use of Kodein in a TornadoFX application, like:

  • Getting control of dependency injection over TornadoFX

  • Defining specific Kodein containers in a TornadoFX / JavaFX graph

  • Overriding a parent Kodein container in a TornadoFX component / JavaFX node

Using TornadoFX Dependency Injection as external source

TornadoFX integrates a dependency injection mechanism to work with its Component. This section will show you how you can interact with the Tornado DI container, by using Kodein. Our goal is to provide you some tools to be able to integrate Kodein as your main DI container, thus by learning and knowing only one mechanism.

  • Defining the external source

    In order to use Kodein upon TornadoFX you should make your App as KodeinAware, then by using the installTornadoSource() extension function, in your kodein property override, you’ll be able to get through the external source from anywhere in your App.

    Example: Installing the TornadoFX DI container as external source for Kodein
    class TornadoApplication : App(MainView::class), KodeinAware {
        override val kodein: Kodein = Kodein {
            installTornadoSource() (1)
         }
    
    }
    1 Branch Kodein upon TornadoFX dependency injection, to be able to access those dependencies through your Kodein container
    We highly recommend that you make your App as KodeinAware in order to benefit from the use of Kodein upon TornadoFX DI container.
  • Retrieving TornadoFX Component through Kodein

    Once you’ll have installed the TornadoFX source, you will be able to retrieve transparently TornadoFX Component through Kodein, as it were one of your own dependencies.

    Example: Retrieving a Component from Kodein
    class PersonListController : Controller() { ... } (1)
    class PersonListView : View() {
        private val listController: PersonListController by kodein().instance() (2)
    }
    1 PersonListController is not bound on the Kodein container, but as a Controller its accessible from TornadoFX DI container
    2 Retrieve PersonListController through the Kodein external source
  • Using TornadoFX scopes to retrieve binding through the external source

    Like Kodein, TornadoFX as scopes to contextualize and attach state to its dependencies. As we can retrieve TornadoFX dependencies through Kodein external source, we also can retrieve them using scopes with Kodein.

    Example: Retrieving a Component from Kodein with a Scope
    class PersonScope(person: Person) : Scope() { (1)
        val model = PersonModel(person)
    }
    //...
    class EditorTabFragment : Fragment() {
        override val scope = super.scope as PersonScope (2)
    }
    //...
    val editor: EditorTabFragment by kodein().on(personScope).instance() (3)
    //...
    1 Define a Scope that will help contextualize Component
    2 Every Component has a scope that can be override, this allow TornadoFX to handle multiple instances with multiple contexts
    3 We can retrieve a scoped instance of EditorTabFragment with the function on(personScope), where personScope is a PersonScope

Defining Kodein container in FX’s Node

For some need we could want to define Kodein containers into the Node hierarchy. To do so we provide an extension function to create a Kodein container attached to a Node. This is done by adding the Kodein container to the properties of the Node, thus we will be able to access it from any child in the hierarchy.

Example: Defining and using a Kodein container from a Node
class MyView : View() {
    override val root = hbox { (1)
        kodein { (2)
            bind<Random>() with singleton { SecureRandom() }
        }

        form { (3)
            val random by kodein().instance<Random>() (4)
        }
    }
}
1 Defining the root Node for a View
2 Declaring a new Kodein container into the root Node, with its binding
3 Defining a child Node
4 Calling the kodein() extension function to access the nearest Kodein container, in this case the one defined at <2>, then retrieve a dependency
This feature will provide a new Kodein container, meaning it won’t be linked to other Kodein container, such as your global one. To keep your binding from the global Kodein container you can use extension or following the next about subKodein

Extending the nearest Kodein container

Some times, we might want to extend an existing Kodein container in a dedicated area, without impacting the whole application. For example, in a login form, we might want to have credentials binding, that would be only available on this form and its children.

To do so, we have facilities to extend a Kodein container by calling the subKodein extension function, available on Component and Node classes.

  1. In the case of a Component

    Example: Extending the nearest Kodein container for a Component
    class LoginController : Controller(), KodeinAware { (1)
        override val kodein: Kodein = subKodein { (2)
                bind<CredentialsDao> with singleton { CredentialsDao() } (3)
        }
    
        // ...
    
        val dao by instance<CredentialsDao>() (4)
    }
    1 Defining your Component as KodeinAware will help keeping track of your extended Kodein container
    2 Extending the nearest Kodein container, here the App one
    3 Defining binding only available for the extended Kodein container
    4 Retrieve the CredentialsDao from the local Kodein container
    Making your Component as KodeinAware is optional, but it will help you keeping track of your extended container. Otherwise, you might want to store a reference of your extended container in a local variable.
  2. In the case of a Node hierarchy

    Example: Extending the nearest Kodein container for a Node
    class LoginView : View() {
        override val root = hbox { (1)
            subKodein { (2)
                bind<LoginController>() with singleton { instance() } (3)
            }
    
            form {
                val controller by kodein().instance<LoginController>() (4)
            }
        }
    }
    1 Defining the root Node for a View
    2 Extending the nearest Kodein container, here the App one, but stored in the Node properties
    3 Defining binding only available for the extended Kodein container
    4 Retrieve the CredentialsDao from the nearest Kodein container, in that case the extended container stored in the hbox properties
In the case of a Component you have to handle the reference of the extended container yourself. But, in the case of a Node the subKodein will manage it, by storing the reference to the extended container, into the Node properties, like for the FX container feature

TornadoFX scopes

Kodein provides a standard scope for any object (TornadoFX or not). The WeakContextScope will keep singleton and multiton instances as long as the context (= object) lives.

That’s why the ComponentScope and NodeScope are just wrappers upon WeakContextScope with the respective targets Component and Node.

  • ComponentScope

    Example: Defining Component scoped dependencies
    val kodein = Kodein {
        bind<EditingState>() with scoped(ComponentScope).singleton { EditingState() } (1)
    }
    1 A EditingState object will be created for each Component that will ask for.
    Example: Retrieving `Component scoped dependencies
    class EditorTabFragment : Fragment() {
        private val editingState: EditingState by kodein().on(this).instance() (1)
    }
    2 Scope is this
  • NodeScope

    Example: Defining Node scoped dependencies
    val kodein = Kodein {
        bind<EditingState>() with scoped(NodeScope).singleton { EditingState() } (1)
    }
    1 A EditingState object will be created for each Node that will ask for.
    Example: Retrieving `Node scoped dependencies
    class PersonEditorController : Controller() {
        val personEditorView: PersonEditorView by kodein().instance()
    
        fun editPerson(person: Person) {
            val tab = personEditorView.tabPane.tab("Tab Title")
            val editingState: EditingState by kodein().on(tab).instance() (1)
        }
    }
    2 Scope is tab a Node element, every Tab would have a different instance of EditingState