Skip to content
Daniel Molinero edited this page Aug 8, 2019 · 9 revisions

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:

Field injection

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)

Constructor injection

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.

Modules & Bindings

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()
}

Scope

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()

Lifecycle & ViewModels

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)