Eager initialization for Kodein
This article explains to use Kodein eagerly. By default it works lazily. Until now I haven’t been able to use it to retrieve a value eagerly, that is without using the keyword val
or var
.
Previously, in order to immediately use a value inside a piece of code instead of having to initialize it lazily, I created a sugar function that first initialized a value eagerly, then immediately returned it:
val Any.log: Logger get() {
val result by instance<String, Logger>(this::class.java.name)
return result
}
The project
The project I am using this solution in is Victor Events. The example I use is creating a per-class instance of Logger
, which was discussed in detail in another article in this blog.
The binding
This is the Kodein binding I created for Logger
. Nothing has changed since the last article, other than adding a @Suppress
annotation for the sake of suppressing a warning:
bind<Logger>() with multiton { tag: String ->
Logger.getLogger(tag)!!.apply {
if (!BuildConfig.DEBUG) {
@Suppress("UsePropertyAccessSyntax")
setFilter { false }
}
}
}
This is the code that retrieves (lazily) an instance of the multiton. Again, nothing has changed in it:
inline fun <reified A, reified T : Any> instance(arg: A) = Kodein.global.instance<A, T>(arg = arg)
Delegated properties
To understand my solution, you may want to see the documentation of delegated properties.
Normally I would just use the value
property of Lazy
delegate, because it forces the value to initialize and immediately returns it. However, because Kodein uses the provideDelegate()
function, it doesn’t give me an instance of Lazy
immediately. Instead, I have to request it. This is the way provideDelegate()
is defined:
operator fun provideDelegate(receiver: Any?, prop: KProperty<Any?>): Lazy<V>
provideDelegate()
takes in two parameters, only one of which may be null
.
This is the Kodein-specific implemenation:
override fun provideDelegate(receiver: Any?, prop: KProperty<Any?>): Lazy<V> = lazy {
@Suppress("UNCHECKED_CAST")
val context = if (receiver != null && originalContext === AnyKodeinContext) KodeinContext(TTOf(receiver) as TypeToken<in Any>, receiver) else originalContext
get(context, true) } .also { trigger?.properties?.add(it)
}
When the first parameter is null
, Kodein just uses the default KodeinContext
, so let’s leave it that way. The second parameter is ignored, so I can pass any value at all, just making sure I do not pass null
.
I need to pass any value of KProperty
as the second parameter. Reflection documentation explains how to create one. In order to do it, I only have to use an arbitrary pre-existing value with the ::
operator. This is how I do it:
inline val <T> KodeinProperty<T>.value get() =
provideDelegate(null, Build::ID).value
The provideDelegate()
function returns an instance of Lazy
, on which I call .value
to immediately retrieve the value.
Usage
This is the code I use to retrieve an instance of Logger
, using the class name as a parameter passed to Kodein:
val Any.log: Logger get() =
instance<String, Logger>(this::class.java.name).value
The above invocation eagerly retrieves an instance from Kodein by first requesting a Lazy delegate and then calling value
on it. It works by calling the extension property KodeinProperty<T>.value
discussed in the preceeding section.
Finally, this is the example use in the actual code of Victor Events:
log.info("firebaseAuthWithGoogle:" + acct.id!!)
The above retrieves an instance of Logger
using the extention property Any.log
. Because I configured Kodein to use a a multiton binding for Logger
, only one instance will be created per class, and the Logger
’s tag will be initiated with that class name. Also, unless the application is a debug build, no logs will be produced
Conclusion
In the present article I explained how I refactored a solution described previously in this blog, which can be used to retrieve a per-class instance of Logger
.
I found this solution more by analyzing the code of Kodein
than by reading a particular piece of documentation, so the code presented herein might be less than optimal. If you think you know of a better solution, please contact me by submitting a new issue on GitHub. Thank you.