stephanenicolas / toothpick

A scope tree based Dependency Injection (DI) library for Java / Kotlin / Android.
Apache License 2.0
1.12k stars 115 forks source link

Inline "Provider" declaration? #403

Closed terekhovyes closed 3 years ago

terekhovyes commented 4 years ago

In other libs (Koin, Kodein) I love simplicity of "Provider" declaration. But I don't see any ways to do it with Toothpick. So instead of writing something like this (look at get() function):

class HelloWorld(val name: String)
class Printer(val helloWorld: HelloWorld)

fun helloModule(name: String) {
    bind<HelloWorld>().toProviderInstance { HelloWorld(name) }.providesSingleton()
    bind<Printer>().toProviderInstance { Printer(get()) }.providerSingleton()
}

I have to write boilerplate Provider classes:

class PrinterProvider @Inject constructor(
    val helloWorld: HelloWorld
) : Provider<Printer> {
    override fun get() = Printer(helloWorld)
}

fun helloModule(name: String) {
    bind<HelloWorld>().toProviderInstance { HelloWorld(name) }.providesSingleton()
    bind<Printer>().toProvider(PrinterProvider::class).providesSingleton()
}

So the questions are:

  1. Can we introduce something like get() function, that inject dependencies from current scope?
  2. Is there workaround to access current Scope right inside module {} function?
  3. If both 1 and 2 are "No" — I will be grateful for a brief explanation, what exactly keeps us from doing! Thank you :)
osipxd commented 4 years ago

About 1'st. Check extension Scope.getInstance()

terekhovyes commented 4 years ago

About 1'st. Check extension Scope.getInstance()

No, you have no access to Scope inside toProviderInstance {} function, so it won't work

osipxd commented 4 years ago

About 1'st. Check extension Scope.getInstance()

No, you have no access to Scope inside toProviderInstance {} function, so it won't work

It is 2'nd question :)

dlemures commented 4 years ago

Not sure if I fully understand the problem correctly looking at the code. With Koin/Kodein you need to define providers for every single class that you want to be able to inject. With TP there is no need for that. Using your example, this would work:

@InjectConstructor
class Printer(val helloWorld: HelloWorld)

fun helloModule(name: String) {
    bind<HelloWorld>().toProviderInstance { HelloWorld(name) }.providesSingleton()
    bind<Printer>().singleton()
}

TP when trying to inject HelloWorld inside Printer would use the one defined inside helloModule.

Still if you have a different use case where you still need to inject something inside the provider, i see 2 options:

  1. Defining a provider class 😜. It is a non-trivial use case, so defining a class for it seems correct.
  2. Providing the scope to the TP module by breaking the injection chain into 2 parts:
    val scope = KTP.openScope(name)
    scope.installModules(module {
    bind<HelloWorld>().toProviderInstance { HelloWorld(name) }.providesSingleton()
    bind<Printer>().toProviderInstance { Printer(scope.getInstance()) } .providesSingleton()
    })
    .inject(this)

Btw, sorry for the delay...

morder commented 3 years ago

everything is working

 class HelloWorld @Inject constructor(val name: String)
 class Printer @Inject constructor(val helloWorld: HelloWorld)

rootScope.installModules(module {
    bind(HelloWorld::class.java).toProviderInstance { HelloWorld("name") }.providesSingleton()
    bind(Printer::class.java).singleton()
})