-
Notifications
You must be signed in to change notification settings - Fork 114
KTP
Toothpick 3.0 comes with KTP, a new layer that provides a Kotlin friendly API. On this page we explain how to use the new 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 creation of Activities and Fragments, as it is handled by the Android framework directly. For those situations, we provide a new Kotlin API that uses Property Delegation.
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 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 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
...
}
Just 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 what 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 module and binding API to take advantage of the rich Kotlin API:
// 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()
}
For the ones that 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()
On 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(this)
.installViewModelBinding(<BackpackViewModel>this)
.inject(this)
Or here a 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)