Last active
April 25, 2019 17:38
-
-
Save stephanenicolas/aa66901deaca65ed220df73d246ad531 to your computer and use it in GitHub Desktop.
TP for Kotlin & java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import toothpick.Scope | |
import toothpick.Toothpick | |
import kotlin.properties.ReadOnlyProperty | |
import kotlin.reflect.KProperty | |
annotation class ActivityScope | |
annotation class FreeScope | |
//------------------------- | |
// case: Entry point | |
//------------------------- | |
//the scope will have to be provided. | |
//we don't use a factory for entry points but member injector will be generated. | |
class FooActivity { | |
@Inject Bar bar; | |
void onCreate(Bundle state) { | |
getScope().inject(this); | |
} | |
public Scope getScope() { | |
Scope scope = Toothpick.openScope("blah") | |
scope.bindScopeAnnotation(ActivityScope.class) | |
//scope.installModule(...) | |
return scope; | |
} | |
} | |
//------------------------- | |
// case: Scoped Non Entry point | |
//------------------------- | |
//this is the case of a presenter for instance that should belong to | |
//the activity scope. Both the generated factory and member injectors are working. | |
//instead of using a custom scope like @ActivityScope, we could use @Scope("ActivityScope") | |
//but it breaks JSR 330 annotation has it has a value, or @Named("ActivityScope") @Scope | |
@ActivityScope | |
class Foo { | |
@Inject Bar bar; | |
} | |
//------------------------- | |
// case: Non Scoped Non Entry point with sub injection | |
//------------------------- | |
//this is the case of a business object that is not scoped | |
//we don't need a scope annotation because the @Inject annotation | |
//will triger the creation of a factory (and a member injector) | |
class Bar { | |
@Inject Bar bar; | |
} | |
//------------------------- | |
// case: Non Scoped Non-Entry point without sub injection | |
//------------------------- | |
//this is the case of a business object that is not scoped | |
//because an annotation is needed to get a factory, | |
//we have an explicit default constructor | |
class Qurtz { | |
@Inject Qurtz() {} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import toothpick.Scope | |
import toothpick.Toothpick | |
import kotlin.properties.ReadOnlyProperty | |
import kotlin.reflect.KProperty | |
annotation class ActivityScope | |
annotation class FreeScope | |
//------------------------- | |
// case: Entry point | |
//------------------------- | |
//the scope will have to be provided. | |
//there is no annotation as we are not using member injectors | |
//and we don't use a factory for entry points | |
class FooActivity { | |
val bar: Bar by scope.inject() | |
val scope: Scope | |
get() { | |
if (Toothpick.isScopeOpen("blah)) return Toothpick.openScope("blah") | |
val scope = Toothpick.openScope("blah") | |
scope.bindScopeAnnotation(ActivityScope::class.java) | |
//scope.installModule(...) | |
return scope | |
} | |
} | |
//------------------------- | |
// case: Scoped Non Entry point | |
//------------------------- | |
//this is the case of a presenter for instance that should belong to | |
//a given scope, and it will retrieve it from the scoped passed as parameter | |
//the scope is used to create the members | |
//the factory generated by @ActivityScope | |
// (nuts it's a custom annotation, it has to be passed to kapt, or we use a fixed one ? @Scope("Activity"), | |
//actually we could use the pattern .*Scope in the AP to deal with all of them, that could be a convention) | |
//will make sure we are using a scope that supports this annotation, passed as a param to the constructor. | |
//this scope will be used for field injection | |
@ActivityScope | |
class FooByFieldsDI(scope: Scope) { | |
val bar: Bar by scope.inject() | |
} | |
//the same achieved by constuctor DI for non entry points (dagger paradigm) | |
//makes the object simpler and indepent from the scope type, which is way better. | |
//we don't need to keep a reference to the scope, properties are already injected not deferred, | |
//even the lazies (and they do contain the scope used to build Foo) | |
//this syntax is also closer to JSR 330 | |
@ActivityScope | |
class FooByConstructorDI @Inject constructor(val bar: Bar, val bar2: Lazy<Bar>) | |
//------------------------- | |
// case: Non Scoped Non Entry point with sub injection | |
//------------------------- | |
//this is the case of a business object that is not scoped | |
//we need to pass the scope as a constructor to get lazy field injection | |
//we need to annotate now so that we can get a factory | |
@FreeScope | |
class BarByFieldsInjectionDI(scope: Scope) { | |
val qurtz: Qurtz by scope.inject() | |
} | |
//same as before. The annotation FreeScope is not even needed to produce a factory here. | |
class BarByConstructorInjectionDI @Inject constructor(val qurtz: Qurtz, val qurtz2: Qurtz) | |
//------------------------- | |
// case: Non Scoped Non-Entry point without sub injection | |
//------------------------- | |
//this is the case of a business object that is not scoped | |
//we do not need to pass the scope as no field is injected, | |
//we can detect this constructor case for code gen of the factory | |
//we need to annotate now so that we can get a factory | |
//an alternative could be to use `@Inject constructor()` | |
@FreeScope | |
class Qurtz | |
//------------------------- | |
// TP Kotlin Extensions | |
//------------------------- | |
//used for the `by` fields injection | |
class TPDelegate<OWNER, T>(val scope: Scope, val clazz: Class<T>) : ReadOnlyProperty<OWNER, T> { | |
override fun getValue(thisRef: OWNER, property: KProperty<*>): T { | |
return scope.getInstance(clazz) | |
} | |
} | |
//provides a delegate per field | |
//I actually wonder about the byte code cost in business classes | |
//that would use a scope as a parameter, it seems to me the cost | |
//of so many inline function overrides for reified types can be huge | |
inline fun <reified T> Scope.inject(): TPDelegate<Any, T> { | |
return TPDelegate(this, T::class.java) | |
} | |
//the convenient `getInstance<Foo>` instead of `getInstance(Foo::class.java)` | |
inline fun <reified T> Scope.getInstance(): T { | |
return getInstance(T::class.java) | |
} | |
fun main() { | |
val scope: Scope = Toothpick.openScope("") | |
val bar = scope.getInstance<Bar>() | |
bar.qurtz | |
} | |
//Generated: basically factories of TP2/3, we don't really need anything else | |
//if only there would be a syntax for factories we wouldn't need them.. | |
@zerobaseindex, I was gonna write the gist using constructor DI for kotlin as it helps removing the scope from the constructor / class definition.
For Bar
, we need a factory to pass the scope too. Indeed we just use factories with a parameter type that we always know how to inject properly. It just reuses the factory code to instantiate an object. We keep the factory mechanism because scope.getinstance(clazz: Class<T>)
cannot invoke a constructor T()
or T(scope)
, it's just not possible to invoke a constructor this way in java or in kotlin. And the factories also enable to use any other kind of parameters, in java or in kotlin.
@zerobaseindex gist updated...
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Interesting approach, overall I like it. I would argue that for
Foo
would be a place where we could be Kotlin opinionated and recommend the use of constructor injection as that doubles for defining them as fields. I'm not sure I follow theBar
example and why one would need to create a factory at all if passing the scope. Is the idea that you would still essentiallygetInstance(Bar::class.java)
and allow TP to create Bar instead of doingBar(scope = myScope)