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
kodein-di-framework-tornadofx-jvm
:-
Add those lines in your
dependencies
block in your applicationbuild.gradle
file:Gradle Groovy scriptimplementation 'org.kodein.di:kodein-di-generic-jvm:6.5.5' implementation 'kodein-di-framework-tornadofx-jvm:6.5.5'
Gradle Kotlin scriptimplementation("org.kodein.di:kodein-di-generic-jvm:6.5.5") implementation("kodein-di-framework-tornadofx-jvm:6.5.5")
either kodein-di-generic-jvm
orkodein-di-erased
must be declared in addition to thekodein-di-framework-tornadofx-jvm
package. -
Declare your
App
asKodeinAware
and implement a Kodein containerExample: a TornadoFX Application declaration,KodeinAware
, using TornadoFX DI as external sourceclass TornadoApplication : App(MainView::class), KodeinAware { (1) override val kodein: Kodein get() = Kodein { (2) installTornadoSource() (3) } }
1 Setting the App
asKodeinAware
will help you define a global Kodein container2 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:
Component
that get the Kodein container from the App
, if its KodeinAware
fun Component.kodein() = kodein { app }
TornadoFX
|
In your code you can access the global Kodein container like the following snippets.
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 |
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
.
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.
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
asKodeinAware
, then by using theinstallTornadoSource()
extension function, in yourkodein
property override, you’ll be able to get through the external source from anywhere in yourApp
.Example: Installing the TornadoFX DI container as external source for Kodeinclass 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
asKodeinAware
in order to benefit from the use of Kodein upon TornadoFX DI container. -
Retrieving TornadoFX
Component
through KodeinOnce 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 Kodeinclass 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 aController
its accessible from TornadoFX DI container2 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 Scopeclass 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 contextualizeComponent
2 Every Component
has a scope that can be override, this allow TornadoFX to handle multiple instances with multiple contexts3 We can retrieve a scoped instance of EditorTabFragment
with the functionon(personScope)
, where personScope is aPersonScope
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.
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 |
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.
-
In the case of a
Component
Example: Extending the nearest Kodein container for aComponent
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
asKodeinAware
will help keeping track of your extended Kodein container2 Extending the nearest Kodein container, here the App
one3 Defining binding only available for the extended Kodein container 4 Retrieve the CredentialsDao
from the local Kodein containerMaking your Component
asKodeinAware
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. -
In the case of a
Node
hierarchyExample: Extending the nearest Kodein container for aNode
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 aView
2 Extending the nearest Kodein container, here the App
one, but stored in theNode
properties3 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: DefiningComponent
scoped dependenciesval 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 dependenciesclass EditorTabFragment : Fragment() { private val editingState: EditingState by kodein().on(this).instance() (1) }
2 Scope is this
-
NodeScope
Example: DefiningNode
scoped dependenciesval kodein = Kodein { bind<EditingState>() with scoped(NodeScope).singleton { EditingState() } (1) }
1 A EditingState
object will be created for eachNode
that will ask for.Example: Retrieving `Node scoped dependenciesclass 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, everyTab
would have a different instance ofEditingState