Replies: 2 comments 5 replies
-
I'm the original author of Anvil for Dagger 2 and implemented the same features for kotlin-inject for our internal project. I'll give a talk about these extensions at Droidcon SF 2024.
This approach seems reasonable and I agree, but the downside is that you open up the door of multiple ways of doing DI and using kotlin-inject, which becomes confusing to users and makes adoption harder. I believe it's okay to be opinionated, have one way to do DI and do it right. I can speak a little bit about how the Anvil features for kotlin-inject are implemented and how the API surface looks like. All of this was built without making any changes to kotlin-inject itself. My biggest concern is that kotlin-inject requires typealiases for qualifiers and arguments for scope annotations aren't supported, e.g. @SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class SessionManagerImpl @Inject constructor(
@ForScope(AppScope::class) private val coroutineScope: CoroutineScope,
) : SessionManager For kotlin-inject we needed to introduce new types for each scope, e.g. for the app scope we had to define: annotation class SingleInAppScope
typealias AppScopeCoroutineScope = CoroutineScope
typealias AppScopeScoped = Scoped This repeats for every other scope. For the Anvil features for kotlin-inject we implemented @ContributesTo
@SingleInAppScope
interface PresenterCoroutineScopeComponent For us it's a requirement to define the scope on the interface. This allows us to know when to merge this interface.
@Component
@MergeComponent
@SingleInAppScope
abstract class AndroidAppComponent : AndroidAppComponentMerged When we see
@Inject
@SingleInAppScope
@ContributesBinding
class CounterImpl : Counter Here some inconsistency comes in. We can only make the connection to the right scope, if the type itself is scoped. If you want to create a new instance every time a type is injected, then you need to remove @ContributesBinding(scope = SingleInAppScope::class) This is rather ugly but required. To find the right bound type, we inspect the super types. If there is more than one super type, then it's required to define the bound type: @Inject
@SingleInAppScope
@ContributesBinding(boundType = Counter::class)
class CounterImpl : Counter, OtherType The last annotation is annotation class ContributesSubcomponent {
annotation class Factory
} This one works very similar to Anvil and no issues there except that we always require a @ContributesSubcomponent
@SingleInRendererScope
interface RendererComponent {
@ContributesSubcomponent.Factory
@SingleInAppScope
interface Parent {
fun rendererComponent(factory: RendererFactory): RendererComponent
}
} This one is very powerful and helps us to reduce boilerplate. We have more annotations for our codebase specifically. TL;DR is that implementing these things wasn't too hard with KSP and how kotlin-inject works today. I didn't encounter any blocker. What would be nice to have is for kotlin-inject to automatically pick up the |
Beta Was this translation helpful? Give feedback.
-
Our code is now public: https://github.com/amzn/kotlin-inject-anvil |
Beta Was this translation helpful? Give feedback.
-
I've gotten several requests for anvil or hilt like features. In order to keep this library simple and flexible I think they would be best implemented on top of kotlin-inject instead, much like those libs are built on top of dagger. The question then becomes should kotlin-inject provide anything to aid in the creation of such things. While doing nothing would be serviceable I think we can offer a nicer experience with a small plugin api.
The proposed idea is for plugins to provide interfaces that the generated component will implement. These interfaces can then provide whatever types they want into the component. For example, an anvil usecase may look like:
and then the plugin will generate
and tell kotlin-inject about this interface so that it will generate
Some thought needs to be put into what exactly this api should look like, I think in general it should provide the component class name and annotations and return what interface(s) to add. Something like:
Beta Was this translation helpful? Give feedback.
All reactions