-
Notifications
You must be signed in to change notification settings - Fork 114
KTP
Toothpick 3.0 comes with KTP: a Kotlin friendly API.
In Kotlin, constructor injection is the preferred way to inject your dependencies. However, that is not always possible. For instance, we have no control over the creations of Activities and Fragments, as they are handled by the Android framework directly. For those situations, we provide a new Kotlin API that uses Property Delegation to enable Field Injection.
In the following example, we show how to inject a single dependency using eager injection.
class MyActivity : AppCompatActivity() {
val myDependency: MyDependency by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
KTP.openScopes(Application, this)
.installModules(myModule())
.inject(this)
}
}
Eager injection means that myDependency
will be injected/created as soon as you call inject(this)
. We also provide the option to do lazy
and provider
dependencies:
class MyActivity : AppCompatActivity() {
// This dependency instance will be created the first time you access
// to it and the same one will be used for the following access
val myLazyDependency: MyDependency by lazy()
// A new instance of this dependency will be used every time you access
// to it. Equivalent to:
// val myProviderDependency get() = MyDependency()
val myProviderDependency: MyDependency by provider()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
KTP.openScopes(Application, this)
.installModules(myModule())
.inject(this)
}
}
Probably, you have noticed that to perform the injections, we use KTP
instead of Toothpick
. KTP
provides the same functionality as Toothpick
but in a Kotlin friendly way. On your java classes you will still use Toothpick
.
Regarding compatibility, KTP
is 100% compatible with your java dependencies, so on the above examples, MyDependency
could be either a Java or a Kotlin class.
It is also possible to use these property delegate methods with named and qualified injections:
val myDependency: MyDependency by inject("name")
val myLazyDependency: MyDependency by lazy(MyQualifierAnnotation::class)
For the rest of your classes, where constructor injection is used, we have introduced a new annotation that makes the class injectable
and get its depencies injected with minor changes to your code:
@InjectConstructor
class MyDependency(val myHelper: MyHelper) {
// myHelper will be injected by Toothpick
...
}
By annotating your class with @InjectConstructor
, the class will be visible for Toothpick and the only available constructor will be used for creation/injection.
The above code is equivalent to:
class MyDependency @Inject constructor(val myHelper: MyHelper) {
...
}
If your class contains more than one constructor, you will have to use the latter in order to specify which constructor should Toothpick use.
It is also possible to inject lazy
and provider
using constructor injection:
@InjectConstructor
class MyDependency(val myHelper: Lazy<MyHelper>, val myOtherHelper: Provider<MyOtherHelper>) {
fun doSomething() {
myHelper.get().helpMeWithSomething()
myOtherHelper.get().helpMeWithSomethingElse()
}
}
However, on Kotlin it is not possible to do constructor injection with delegated getter, which means that you will need use use the Lazy
and Provider
classes and call get()
to retrieve the instance. The same as with vanilla Toothpick.
KTP also introduces a new binding API to take advantage of the Kotlin features:
// new way to create a inlined Module
module {
// equivalent to bind(MyDependency::class.java)
bind<MyDependency>()
// equivalent to bind(IDependency::class.java).to(MyDependency::class.java)
bind<IDependency>().toClass<MyDependency>()
// equivalent to bind(IDependency::class.java).toInstance(MyDependency())
bind<IDependency>().toInstance { MyDependency() }
// equivalent to bind(IDependency::class.java).toProvider(MyDependency::class.java)
bind<IDependency>().toProvider(MyDependencyProvider::class)
// equivalent to bind(IDependency::class.java).toProviderInstance(MyDependencyProvider())
bind<IDependency>().toProviderInstance(MyDependencyProvider())
bind<IDependency>().toProviderInstance {
// my provider defined in place
MyDependency()
}
// And we can still apply all the name, scope and retention modifiers to the above bindings
bind<IDependency>().withName("name").toClass<MyDependency>()
bind<IDependency>().withName(QualifierName::class).toClass<MyDependency>()
bind<IDependency>().toClass<MyDependency>().singleton()
bind<IDependency>().toClass<MyDependency>().singleton().releasable()
bind<IDependency>().toProvider(MyDependency::class).providesSingleton()
bind<IDependency>().toProvider(MyDependency::class).providesSingleton().providesReleasable()
bind<IDependency>().toProvider(MyDependency::class).providesSingleton().providesReleasable().singleton()
}
As you can see on the above example, creating inlined modules now it is easier:
KTP.openScopes(Application, this)
.installModules(module {
bind...
})
.inject(this)
For those who like Service Locator style, the Scope
class has been extended in order to be able to do the following:
val myDependency: MyDependency = scope.getInstance()
val myDependency: Lazy<MyDependency> = scope.getLazy()
val myDependency: Provider<MyDependency> = scope.getProvider()
The scope here would have to be passed as a constructor parameter property.
In Toothpick 3, we have introduced have few extensions to make Scopes
lifecycle and ViewModels aware. For Java, they are static util methods, but for Kotlin they are extension functions:
// Close Scope when the Activity/Fragment is destroyed
KTP.openScopes(this)
.closeOnDestroy(this)
.inject(this)
// Close Scope when the ViewModel attached to the Activity/Fragment is cleared
KTP.openScopes(this)
.closeOnViewModelCleared(this)
.inject(this)
// Create binding on the scope to be able to inject the ViewModel
KTP.openScopes(<name>)
.installViewModelBinding(<BackpackViewModel>this)
.inject(this)
Or here an advanced example using the new configuration lambda API:
KTP.openScopes(ApplicationScope::class.java)
.openSubScope(ViewModelScope::class.java) { scope: Scope ->
scope.installViewModelBinding<BackpackViewModel>(this)
.closeOnViewModelCleared(this)
.installModules(module {
bind<Backpack>().singleton()
})
}
.openSubScope(this)
.installModules(module {
bind<IBackpackAdapter>().toClass<BackpackAdapter>()
})
.closeOnDestroy(this)
.inject(this)
As the rest of the Toothpick 3 project, KTP is compliant with incremental annotation processing when using kapt.