-
-
Save stephanenicolas/aa66901deaca65ed220df73d246ad531 to your computer and use it in GitHub Desktop.
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() {} | |
} | |
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.. | |
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 the Bar
example and why one would need to create a factory at all if passing the scope. Is the idea that you would still essentially getInstance(Bar::class.java)
and allow TP to create Bar instead of doing Bar(scope = myScope)
@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...
That's a an example of a simple syntax for TPK.
The worse of it is the constructor but we can enforce it at build time. Classes / Objects would depend directly on TP in their API.
The syntax is simple though. I am not sure we can achieve a library that would also work well for java and kotlin at the same time.