Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KTP draft specs #316

Closed
dlemures opened this issue Dec 4, 2018 · 14 comments
Closed

KTP draft specs #316

dlemures opened this issue Dec 4, 2018 · 14 comments
Milestone

Comments

@dlemures
Copy link
Collaborator

dlemures commented Dec 4, 2018

Kotlin Toothpick draft specs - WIP

Use delegate properties for field injection

We should not use annotations for field injection, instead we will use delegate properties. For example:

by ktp.inject() // default scope
by ktp.lazy() // lazy
by ktp.provider() // provider

Moreover, there should be a way to:

  • Specify when to use the default scope and when to wait for a scope to be provided.
  • Use a named injection.

Kotlin friendly API for

Generate code compatible with optional parameters (possible?)

WIP

Moreover, KTP will be based on TP 2.0 -> no registries (no configuration needed), incremental annotation processing, better scope annotations, ... (#315, #225)

Something you miss? Any suggestion? Please comment 🙂

@code-twister
Copy link

Can we have something like this for named injections:

by ktp.named("THE_NAME").inject() // default scope

@PaulWoitaschek
Copy link

PaulWoitaschek commented Dec 4, 2018

So where does ktp come from? And where do I specify the scope?

Lets say I have the following class:

class MyInjectable : MyFrameworkClass(){

  @field[Inject Named("whatever")] lateinit var myNamedClass : Sth
  @Inject lateinit var sthElse : SthElse

  override fun onFrameworkClassInitialized() {
    val scope = Toothpick.openScopes(application, this)
    Toothpick.inject(scope, this)
  }
}

How would that look using the new api?

@code-twister
Copy link

I think I can answer some of you questions @PaulWoitaschek:

  1. KTP comes from: Kotlin ToothPick
  2. Based on the initial KTP proposal the class you provided would look something like:
class MyInjectable : MyFrameworkClass(){

  val myNamedClass : Sth by ktp.named("whatever").inject()
  val sthElse : SthElse by ktp.inject()

  ...
}

Note the lack of lateinit or the annotations. Another small change but with a big impact: you can use val instead of var. I think it's a good idea to go with delegated properties (https://kotlinlang.org/docs/reference/delegated-properties.html). The code looks much cleaner.

I think scopes require further investigation on how they can be achieved in pure kotlin and with the new API.

@PaulWoitaschek
Copy link

With comes from I don't mean where the name comes from but rather: where does the reference come from?

If it's a top-level property we can use this only without scopes.
I don't currently see a way of archieving this without the annotation.

@dimsuz
Copy link

dimsuz commented Dec 4, 2018

I also wonder will constructor injection be affected somehow? Field injection lies more in the anti-pattern direction in the DI world, so I hope constructor injection will still work or get some new ideas for improvements too.

@GisoBartels
Copy link

For scopes, what do you think about this idea?

interface ToothpickScope {
    val parentScope: ToothpickScope
        get() = NO_SCOPE
}

val NO_SCOPE = object : ToothpickScope {}

operator fun ToothpickScope.plus(otherScope: ToothpickScope) = when {
    this === NO_SCOPE -> otherScope
    otherScope === NO_SCOPE -> this
    else -> TODO("implement me")
}

fun ToothpickScope.openScope() = Toothpick.openScope(this + parentScope)

fun ToothpickScope.closeScope() {
    Toothpick.openScope(this + parentScope).close()
}

inline fun <reified T> ToothpickScope.getInstance(): T = openScope().getInstance(T::class.java)

inline fun <reified T> ToothpickScope.scopedInject() = object : ReadOnlyProperty<ToothpickScope, T> {
    override fun getValue(thisRef: ToothpickScope, property: KProperty<*>): T {
        return thisRef.getInstance(T::class.java)
    }
}

@code-twister
Copy link

@dimsuz constructor injections are indeed preferred in Java DI frameworks, and also when we're talking about DI in general most of us think about JSR330. Not sure this applies to Kotlin though. I believe we should use the community brainpower to figure out something that's more suited for Kotlin and what this language has to offer, rather than limiting ourselves to what DI meant for Java.

A DI framework is supposed to make dependency injection easy. KTP could be just that tailored specifically for Kotlin.

Also I think if you like how Toothpick for java works, I believe you could still use it in Kotlin it would just be a bit different (maybe more verbose) and you would depend on a Java module.

@PaulWoitaschek
Copy link

I think constructor injection is the preferred way for dependency injection on kotlin too.
It's explicit. You don't have to run your tests to know you forgot dependencies when refactoring.

@stephanenicolas
Copy link
Owner

stephanenicolas commented Dec 5, 2018 via email

@jungc
Copy link

jungc commented Dec 5, 2018

@code-twister

Good point regarding the named injection.
Named and Inject are separate annotations, but if we are going for builder like syntax, we should also consider using named argument

by ktp.named("THE_NAME").inject() // default scope
by ktp.inject(named = "<some name>") // default scope

@chenthorne
Copy link
Collaborator

Related to this discussion, I've proposed some TP 2.0 compatible kotlin extensions in another issue (#305) that may be of interest in defining KTP, would appreciate any feedback.

@code-twister
Copy link

Hey @chenthorne, I really love what you did there. The only thing I would probably do differently is to have all the Android specifics in a different artifact so it can be used with non Android projects. I'm definitely going to use the code you've written.

@chenthorne
Copy link
Collaborator

@code-twister I would do exactly the same! :) Just easier to roll it into one for now to get feedback

@stephanenicolas stephanenicolas mentioned this issue Jun 25, 2019
19 tasks
@stephanenicolas stephanenicolas added this to the 3.0.0 milestone Aug 5, 2019
@stephanenicolas
Copy link
Owner

This is getting closed as it has been released in version 3.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants