-
-
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.