Giter Site home page Giter Site logo

kotlin-inject's Introduction

kotlin-inject

CircleCI Maven Central Sonatype Snapshot

A compile-time dependency injection library for kotlin.

@Component
abstract class AppComponent {
    abstract val repo: Repository

    @Provides
    protected fun jsonParser(): JsonParser = JsonParser()

    protected val RealHttp.bind: Http
        @Provides get() = this
}

interface Http

@Inject
class RealHttp : Http

@Inject
class Api(private val http: Http, private val jsonParser: JsonParser)

@Inject
class Repository(private val api: Api)
val appComponent = AppComponent::class.create()
val repo = appComponent.repo

Download

Using ksp

settings.gradle

pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
}

build.gradle

plugins {
    id("org.jetbrains.kotlin.jvm") version "1.9.0"
    id("com.google.devtools.ksp") version "1.9.0-1.0.13"
}

repositories {
    mavenCentral()
    google()
}

dependencies {
    ksp("me.tatarka.inject:kotlin-inject-compiler-ksp:0.6.3")
    implementation("me.tatarka.inject:kotlin-inject-runtime:0.6.3")
}

or with KAPT (deprecated)

plugins {
  id("org.jetbrains.kotlin.jvm") version "1.9.0"
  id("org.jetbrains.kotlin.kapt") version "1.9.0"
}

dependencies {
  kapt("me.tatarka.inject:kotlin-inject-compiler-kapt:0.6.3")
  implementation("me.tatarka.inject:kotlin-inject-runtime:0.6.3")
}

Usage

Let's go through the above example line-by line and see what it's doing.

@Component
abstract class AppComponent {

The building block of kotlin-inject is a component which you declare with an @Component annotation on an abstract class. An implementation of this component will be generated for you.

    abstract val repo: Repository

In your component you can declare abstract read-only properties or functions to return an instance of a given type. This is where the magic happens. kotlin-inject will figure out how to construct that type for you in it's generated implementation. How does it know how to do this? There's a few ways:

@Provides
protected fun jsonParser(): JsonParser = JsonParser()

For external dependencies, you can declare a function or read-only property in the component to create an instance for a certain type. kotlin-inject will use the return type to provide this instance where it is requested.

Note: It is good practice to always explicitly declare the return type, that way it's clear what type is being provided. It may not always be what you expect!

protected val RealHttp.bind: Http
    @Provides get() = this

You can declare arguments to a providing function/property to help you construct your instance. Here we are taking in an instance of RealHttp and providing it for the interface Http. You can see a little sugar with this as the receiver type for an extension function/property counts as an argument. Another way to write this would be:

@Provides
fun http(http: RealHttp): Http = http
@Inject
class RealHttp : Http

@Inject
class Api(private val http: Http, private val jsonParser: JsonParser)

@Inject
class Repository(private val api: Api)

For your own dependencies you can simply annotate the class with @Inject. This will use the primary constructor to create an instance, no other configuration required!

val appComponent = AppComponent::class.create()
val repo = appComponent.repo

Finally, you can create an instance of your component with the generated .create() extension function.

Features

Component Arguments

If you need to pass any instances into your component you can declare them as constructor args. You can then pass them into the generated create function. You can optionally annotate it with @Provides to provide the value to the dependency graph.

@Component
abstract class MyComponent(@get:Provides protected val foo: Foo)
MyComponent::class.create(Foo())

If the argument is another component, you can annotate it with @Component and it's dependencies will also be available to the child component. This allows you to compose them into a graph.

@Component
abstract class ParentComponent {
    @Provides
    fun provideFoo(): Foo = ...
}

@Component
abstract class ChildComponent(@Component val parent: ParentComponent) {
    abstract val foo: Foo
}
val parent = ParentComponent::class.create()
val child = ChildComponent::class.create(parent)

Type Alias Support

If you have multiple instances of the same type you want to differentiate, you can use type aliases. They will be treated as separate types for the purposes of injection.

typealias Dep1 = Dep
typealias Dep2 = Dep

@Component
abstract class MyComponent {
    @Provides
    fun dep1(): Dep1 = Dep("one")

    @Provides
    fun dep2(): Dep2 = Dep("two")

    @Provides
    fun provides(dep1: Dep1, dep2: Dep2): Thing = Thing(dep1, dep2)
}

@Inject
class InjectedClass(dep1: Dep1, dep2: Dep2)

Function Injection

You can also use type aliases to inject into top-level functions. Annotate your function with @Inject and create a type alias with the same name.

typealias myFunction = () -> Unit

@Inject
fun myFunction(dep: Dep) {
}

You can then use the type alias anywhere and you will be provided with a function that calls the top-level one with the requested dependencies.

@Inject
class MyClass(val myFunction: myFunction)

@Component
abstract class MyComponent {
    abstract val myFunction: myFunction
}

You can optionally pass explicit args as the last arguments of the function.

typealias myFunction = (String) -> String

@Inject
fun myFunction(dep: Dep, arg: String): String = ...

Scopes

By default kotlin-inject will create a new instance of a dependency each place it's injected. If you want to re-use an instance you can scope it to a component. The instance will live as long as that component does.

First create your scope annotation.

@Scope
@Target(CLASS, FUNCTION, PROPERTY_GETTER)
annotation class MyScope

Then annotate your component with that scope annotation.

@MyScope
@Component
abstract class MyComponent()

Finally, annotate your provides and @Inject classes with that scope.

@MyScope
@Component
abstract class MyComponent {
    @MyScope
    @Provides
    protected fun provideFoo(): Foo = ...
}

@MyScope
@Inject
class Bar()

Component Inheritance

You can define @Provides and scope annotations on an interface or abstract class that's not annotated with @Component. This allows you to have multiple implementations, which is useful for things like testing. For example, you can have an abstract class like

@NetworkScope
abstract class NetworkComponent {
    @NetworkScope
    @Provides
    abstract fun api(): Api
}

Then you can have multiple implementations

@Component
abstract class RealNetworkComponent : NetworkComponent() {
    override fun api(): Api = RealApi()
}

@Component
abstract class TestNetworkComponent : NetworkComponent() {
    override fun api(): Api = FakeApi()
}

Then you can provide the abstract class to your app component

@Component abstract class AppComponent(@Component val network: NetworkComponent)

Then in your app you can do

AppComponent::class.create(RealNetworkComponent::class.create())

and in tests you can do

AppComponent::class.create(TestNetworkComponent::class.create())

Multi-bindings

You can collect multiple bindings into a Map or Set by using the @IntoMap and @IntoSet annotations respectively.

For a set, return the type you want to put into a set, then you can inject or provide a Set<MyType>.

@Component
abstract class MyComponent {
    abstract val allFoos: Set<Foo>

    @IntoSet
    @Provides
    protected fun provideFoo1(): Foo = Foo("1")

    @IntoSet
    @Provides
    protected fun provideFoo2(): Foo = Foo("2")
}

For a map, return a Pair<Key, Value>.

@Component
abstract class MyComponent {
    abstract val fooMap: Map<String, Foo>

    @IntoMap
    @Provides
    protected fun provideFoo1(): Pair<String, Foo> = "1" to Foo("1")

    @IntoMap
    @Provides
    protected fun provideFoo2(): Pair<String, Foo> = "2" to Foo("2")
}

Function Support & Assisted Injection

Sometimes you want to delay the creation of a dependency or provide additional params manually. You can do this by injecting a function that returns the dependency instead of the dependency directly.

The simplest case is you take no args, this gives you a function that can create the dep.

@Inject
class Foo

@Inject
class MyClass(fooCreator: () -> Foo) {
    init {
        val foo = fooCreator()
    }
}

If you define args, you can use these to assist the creation of the dependency. To do so, mark these args with the @Assisted annotation. The function should take the same number of assisted args in the same order.

@Inject
class Foo(bar: Bar, @Assisted arg1: String, @Assisted arg2: String)

@Inject
class MyClass(fooCreator: (arg1: String, arg2: String) -> Foo) {
    init {
        val foo = fooCreator("1", "2")
    }
}

Lazy

Similarly, you can inject a Lazy<MyType> to construct and re-use an instance lazily.

@Inject
class Foo

@Inject
class MyClass(lazyFoo: Lazy<Foo>) {
    val foo by lazyFoo
}

Default Arguments

You can use default arguments for parameters you inject. If the type is present in the graph, it'll be injected, otherwise the default will be used.

@Inject class MyClass(val dep: Dep = Dep("default"))

@Component abstract class ComponentWithDep {
  abstract val myClass: MyClass
  @Provides fun dep(): Dep = Dep("injected")
}
@Component abstract class ComponentWithoutDep {
  abstract val myClass: MyClass
}

ComponentWithDep::class.create().myClass.dep // Dep("injected")
ComponentWithoutDep::class.create().myClass.dep // Dep("default")

Options

You can provide some additional options to the processor.

  • me.tatarka.inject.enableJavaxAnnotations=true

    @javax.inject.* annotations can be used in in addition to the provided annotations. This can be useful if you are migrating existing code or want to be abstracted from the injection lib you are using on the jvm.

  • me.tatarka.inject.generateCompanionExtensions=true

    This will generate the create() methods on the companion object instead of the component's class. This allows you to do MyComponent.create() instead of MyComponent::class.create(). However, due to a kotlin limitation you will have to explicitly specify a companion object for your component.

    @Component abstract class MyComponent {
      companion object
    }
  • me.tatarka.inject.dumpGraph=true

    This will print out the dependency graph when building. This can be useful to help debug issues.

Additional docs

You can find additional docs on specific use-cases in the docs folder.

Samples

You can find various samples here

kotlin-inject's People

Contributors

andrey-bolduzev avatar bradynpoulsen avatar evant avatar eygraber avatar humblehacker avatar japplin avatar jstarczewski avatar kaushikgopal avatar kernald avatar paulwoitaschek avatar saket avatar sebaslogen avatar simonmarquis avatar stavfx avatar yonghanju avatar zmarkan avatar zsqw123 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kotlin-inject's Issues

Still Alive?

Hi, thanks for this neat injection framework. Is it still under active development?

Allow mutlithreading on kotlin native

Right now components can only be accessed on the main thread due to the LazyMap implementation. Need to figure out how to allow multithreading like what's available on the jvm. The solution should also work with kotlin's new memory model.

Generates incorrect impl when 2 inner classes have the same name

Had:

class FirstTest {
  @Component abstract class TestComponent
}

class SecondTest {
  @Component abstract class TestComponent
}

it correctly generated the files:

InjectFirstTest_TestComponent
InjectSecondTest_TestComponent

but the impls where both for FirstTest.TestComponent

note: ran into the issue when classes where at the top-level (not within a package), haven't investigate if it still happens otherwise.

can workaround it by renaming one of the components.

Incorrect generated code for accessing an extension provides on a parent component

interface Foo

@Inject
class FooImpl : Foo


@Component
abstract class ParentComponent {
    @Provides
    val FooImpl.bind: Foo
        get() = this
}

@Component
abstract class ChildComponent(@Component val parentComponent: ParentComponent) {
    abstract val foo: Foo
}

expected:

class InjectChildComponent(
  parentComponent: ParentComponent
) : ChildComponent(parentComponent) {
  override val foo: Foo
    get() = with(parentComponent) { FooImpl().bind }}

actual:

class InjectChildComponent(
  parentComponent: ParentComponent
) : ChildComponent(parentComponent) {
  override val foo: Foo
    get() = parentComponent.FooImpl().bind}

Use type alias in generated override if available

for:

typealis App = @Composable () -> Unit

class MyComponent {
    val app: App
}

expected:

class InjectMyComponent {
    override val app: App
        get() = { ...
}

actual:

class InjectMyComponent {
    override val app: Function0<Unit>
        get() = { ...
}

this ensures any annotations (@Composable in this case) are applied to the implementation.

Injected nullable function argument loses nullability

Expected:

@Inject class Foo(bar: Bar?)

@Component abstract class MyComponent { abstract val foo: (Bar?) -> Foo }

to compile but fails because it generates:

class InjectMyComponent { override val foo: Function0<Bar> get() = ... }

Support annotating constructor with @Inject

Annotating the class selects the primary constructor which is usually what you want, but sometimes you'd want to annotate the constructor explicitly. This is useful if:

  1. You have more than one constructor
  2. You are migrating dagger code

Print full name of nested types in error messages

When kotlin-inject is unable to find a provider for a type that's nested inside another type, it only prints its simple name. For example, Factory instead of SomeClass.Factory. This makes it slightly difficult to immediately recognize the type's name. Can the name of nested types prefixed with their parent class(es)?

- e: [ksp] Cannot find an @Inject constructor or provider for: Factory
+ e: [ksp] Cannot find an @Inject constructor or provider for: SomeClass.Factory

Invalid code generated if parent component is missing val

If you have

@Component abstract class MyComponent(@Component parent: ParentComponent)

then generated code will fail to access parent as it's not a val. Quick fix: this should throw an error. May investigate supporting not having val here.

Support typealias for @IntoMap

Normally typealiases are treated as separate types, but here it should look through them to see the Pair as it's useful to re-use across provides.

typealias MyEntry = Pair<MyKey, MyValue>

@Component
abstract class MyComponent {
   @Provides @IntoMap
   fun entry1(): MyEntry = ...
   @Provides @IntoMap
   fun entry2(): MyEntry = ...
}

Support Set<() -> Foo> & Set<Lazy<Foo>>

Extend @IntoSet to work with lambda & lazy like you can with single values.

If you have

@Component
abstract class MyComponent {
  abstract fun provideFooSet() : Set<() -> Foo>
  abstract fun lazyFooSet() : Set<Lazy<Foo>>

  @Provides @IntoSet fun foo1() : Foo = ...
  @Provides @IntoSet fun foo2(): Foo = ...
}

It should generate

class InjectMyComponent : MyComponent {
  override fun provideFooSet() : Set<() -> Foo> = setOf({ foo1() }, { foo2() })
  override fun lazyFooSet(): Set<Lazy<Foo>> = setOf(lazy { foo1() }, lazy { foo2() })
}

graph validation

Need to validate the graph (ex: detect cycles) to give better information on failure cases.

Incorrectly allowing the same scope on a child and parent component.

It should be an error to declare the same scope on a child and parent component as it's not clear which one should provide the dependency. Apparently the following does not produce such an error and instead leads to surprising results.

@Scope
annotation class ScreenScope

@ScreenScope
@Inject
class Foo

class ParentScreen {
  private val component = ParentScreenComponent::class.create()

  private val childScreen by lazy {
    component.childScreen
  }
}

@ScreenScope
@Component
abstract class ParentScreenComponent {
  abstract val childScreen: ChildScreen

  @ScreenScope
  @Provides
  fun parentComponent() = this
}

@Inject
class Bar(foo: Foo)

@Inject
class ChildScreen(
  private val parentComponent
) {
  private val childComponent = ChildScreenComponent::class.create(parentComponent)
  private val bar: Bar by lazy {
    childComponent.bar
  }
}

@ScreenScope
@Component
abstract class ChildScreenComponent(
  @Component val parentComponent: ParentScreenComponent
) {
  abstract val bar: Bar
}

Runtime annotation retention causes warning in JS targets

All of the annotations in the runtime library are marked with @Retention(RUNTIME). It's not immediately obvious to me what purpose this serves since my understanding is that they're only used at compile time(?), but either way; using these annotations in a JS sourceset produces the following warning:

Reflection is not supported in JavaScript target, therefore you won't be able to read this annotation in run-time

Ideally it would produce no warnings.

Happy to open a PR to change the retention annotations, but just wanted to open a discussion first.

Replacing dependencies for tests

Need to figure out a strategy for this that feels good. You can sorta get there with component interfaces but it can get clunky splitting out deps to replace a specific thing sometimes.

Conflicting declarations for scoped `@Provides` functions with parameterized return type

If I have:

import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Scope

@Scope annotation class MyScope

@MyScope @Component abstract class MyComponent {
  abstract val foo: Foo
 
  @MyScope @Provides protected fun t1() = T<Int>()
 
  @MyScope @Provides protected fun t2() = T<String>()
}

class T<T>

@MyScope @Inject class Foo(
  private val t1: T<Int>,
  private val t2: T<String>,
  private val bar: Bar
)

@MyScope @Inject class Bar(
  private val t1: T<Int>,
  private val t2: T<String>
)

then the generated properties for both are named _t.

Generate getters for common object construction

Right now the entire constructor/provides call is generated every place a dependency is used. This leads to a bit of code bloat. Should be able to pull up the construction into a private getter if it's used more than once in a component.

ex:

class InjectMyComponent : MyComponent() {
  private val _foo: Foo
    get() = Foo(...)

  override val bar1: Bar1
    get() = Bar1(_foo)

  override val bar2: Bar2
    get() = Bar2(_foo)

Unable to resolve viewbinding classes.

If you reference a generated viewbinding class in your component or @Inject constructor, it will cause an error. I suspect this is because ksp runs before the viewbinding code gen. You can work around it by wrapping the class so it is resolved:

@JvmInline
value class Binding(val value: MainActivityBinding)

@Component MainActivityComponent(@get:Provides val binding: Binding) { ... }

@Inject class MainScreen(binding: Binding) {
   private val binding = binding.value
   ...
}

Incorrect code generated when scoped compoent has a non-scoped parent component

@CustomScope @Inject class Foo

@Component abstract class ParentComponent

@CustomScope @Component abstract class ChildComponent(@Component val parent: ParentComponent) {
    abstract val foo: Foo
}

expected:

class InjectChildComponent(parent: ParentComponent): ChildComponent(parent) {
    val _foo by lazy { Foo() }
}

actual:

class InjectChildComponent(parent: ParentComponent): ChildComponent(parent) {
    val _foo: Foo by lazy { (parent as InjectFoo)._foo }
}

Support receivers on inject functions

Currently the following generates incorrect code:

typealias foo = String.() -> String
@Inject
fun String.foo(bar: Bar): String = ...

@Component
abstract class MyComponent {
  abstract val foo: foo
}

Support injecting companion object

This generates incorrect code

interface IFoo {
  @Inject
  companion object : IFoo
}

@Component
abstract class MyComponent {
   abstract val foo: IFoo
   val IFoo.Companion.bind: IFoo @Provides get() = this
}

expected:

class InjectMyComponent : MyComponent {
  override val foo: IFoo get() = IFoo.bind
}

actual:

class InjectMyComponent : MyComponent {
  override val foo: IFoo get() = IFoo.Companion().bind
}

Parameters with default values

class Foo(
  val generator: () -> String = { "bar" }
)

For constructors (or functions) with parameters that have default values, can kotlin-inject ignore injecting them? I have a lambda parameter with a default value that I'm only overriding in tests so I don't wanna add a provider for it to my component.

Remove java.lang.String import from generated components

This snippet

import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides

fun main() {
   TestComponent::class.create("Hello World").factory.print()
}

@Component
abstract class TestComponent(@get:Provides protected val string: String) {
   abstract val factory: HelloWorldFactory
}

@Inject class HelloWorldFactory(private val string: String) {

   fun print() = println(string)
}

Generates this:

import java.lang.String
import kotlin.reflect.KClass

class InjectTestComponent(
  string: String
) : TestComponent(string) {
  override val factory: HelloWorldFactory
    get() = HelloWorldFactory(string)}

fun KClass<TestComponent>.create(string: String): TestComponent = InjectTestComponent(string)

Which yields this error:

Type mismatch.
Required:java.lang.String
Found:kotlin.String

image

Allow annotating interfaces with @Component

while they won't be able to take arguments, there's nothing otherwise stopping you from using an interface instead of an abstract class for your @Component impl.

@Component
interface MyComponent {
   val foo: Foo
}

Support suspend

These should all work

@Component abstract class MyComponent {
   abstract val foo: suspend () -> Foo

   @Provides fun provideFoo(): suspend () -> Foo = { Foo() }

   abstract suspend fun bar(): Bar

   @Provides suspend fun providesSuspendBar(): Bar = Bar()
}

@Inject class Baz(foo: suspend () -> Foo, bar: suspend () -> Bar)

Add default values for component constructor arguments which are components with no constructor arguments

Example Code:

@Component abstract class NoArgComponent {
@get:Provides val string: String = "Hi"
}

@Component abstract class MyComponent(@Component component: NoArgComponent)

Generates:

InjectMyComponent(component: NoArgComponent = InjectNoArgComponent()) : MyComponent(component)

This feature provides functionality similar to how dagger can build modules which don't have any constructor params.

Support injecting objects

@Inject object Foo

can be useful if you want to be able to change it from a singleton to an instance later.

Scope defined in seperate module doesn't work.

If you define a scoped inject in module A:

@MyScope @Inject class Foo

and a scoped component in module B:

@MyScope @Component abstract class MyComponent {
   abstract val foo: Foo
}

then the generated component will not include the scoped dep

class InjectMyComponent : MyComponent {
   override val foo: Foo get() = _foo // _foo not generated.
}

this is because only the current module is scanned for @MyScope dependencies, not other dependent modules.

The above can be solved partially by noticing we need a Foo when generating MyComponent and emitting the generated field. However, we don't always know that the field is needed. For example say you have a component

@Component abstract class MyChildComponet(@Component val parent: MyComponent) {
   abstract val foo: Foo
}

in module C. It will expect to reference the field in MyComponent but it may not be generated if MyComponent never uses it itself.

Some possible solutions:

  1. Do what dagger does and force you to declare scoped dependencies in your component (i.e. always require you to declare abstract val foo: Foo). This guarantees MyComponent always knows about Foo when it's generated but increases boilerplate.

  2. Figure out how to scan the classpath for @MyScope annotated dependencies. Not sure how possible this is in the kapt/ksp backends and what the compiler costs are for doing it.

  3. Use a dynamic map to hold scoped dependencies instead of fields.

class InjectMyComponent : MyComponent {
   _scoped = mutableMapOf<String, Any?>()
}

class InjectMyChildComponent(val parent: MyComponent) : MyChildComponent(parent) {
    override val foo: Foo get() = synchronized(parent._scoped) { parent._scoped.getOrPut("Foo") { Foo() } as Foo }
}

this means MyComponent doesn't ever have to know about it's scoped dependencies. The downside is the generated code is less clear to read and there may be some performance costs (unclear).

Function Reference injection doesn't work unless provided type is specified explicitly

If I want to inject a function reference into a class, the build fails unless I explicitly specify the provided type for the function reference in the component. Without the return type, it defaults to the behaviour of trying to generate a provider and in the following example complains that Any can't be provided.

i.e.
this does not work:

@Inject
class MyClass(private val myProvider: suspend () -> Any)

suspend fun theProvider(): Any { /* ... */ }

@Component
abstract class AppComponent {
  @Provides
  protected fun provideTheProvider() = ::theProvider
}

but this does:

@Inject
class MyClass(private val myProvider: suspend () -> Any)

suspend fun theProvider(): Any { /* ... */ }

@Component
abstract class AppComponent {
  @Provides
  protected fun provideTheProvider(): suspend () -> Any = ::theProvider
}

I'm not sure if this is intended behaviour or not, but if it is then it might be worth documenting.

error: @Component class: AppComponent must be abstract

build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.5.0"
    kotlin("kapt") version "1.5.0"
}

repositories {
    mavenCentral()
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}

dependencies {
    kapt("me.tatarka.inject:kotlin-inject-compiler-kapt:0.3.3")
    implementation("me.tatarka.inject:kotlin-inject-runtime:0.3.3")
}

Code:

import me.tatarka.inject.annotations.Component

@Component
abstract class AppComponent

fun main() {

}

Full error:

> Task :kaptKotlin FAILED
D:\.projects\test\build\tmp\kapt3\stubs\main\AppComponent.java:6: error: @Component class: AppComponent must be abstract
public abstract class AppComponent {
                ^
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':kaptKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
   > java.lang.reflect.InvocationTargetException (no error message)

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':kaptKotlin'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:187)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:268)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:185)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:173)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:408)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:395)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:388)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:374)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: org.gradle.workers.internal.DefaultWorkerExecutor$WorkExecutionException: A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
        at org.gradle.workers.internal.DefaultWorkerExecutor$WorkItemExecution.waitForCompletion(DefaultWorkerExecutor.java:342)
        at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForItemsAndGatherFailures(DefaultAsyncWorkTracker.java:142)
        at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForItemsAndGatherFailures(DefaultAsyncWorkTracker.java:94)
        at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForAll(DefaultAsyncWorkTracker.java:80)
        at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForCompletion(DefaultAsyncWorkTracker.java:68)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$2.run(ExecuteActionsTaskExecuter.java:506)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:483)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:466)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$300(ExecuteActionsTaskExecuter.java:105)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.executeWithPreviousOutputFiles(ExecuteActionsTaskExecuter.java:270)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:248)
        at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:83)
        at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:37)
        at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:50)
        at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:47)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:47)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:37)
        at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:68)
        at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:38)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:50)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:36)
        at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41)
        at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74)
        at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:51)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:29)
        at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:54)
        at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:35)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:60)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:27)
        at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:174)
        at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:74)
        at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:45)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:40)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:29)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:99)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:92)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:52)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:36)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:84)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:41)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:91)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:49)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:78)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:49)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:105)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:50)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:86)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:86)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:32)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
        at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:43)
        at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:31)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution$2.withWorkspace(ExecuteActionsTaskExecuter.java:283)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
        at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
        at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
        at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:49)
        at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:35)
        at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:184)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:173)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:408)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:395)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:388)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:374)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at org.jetbrains.kotlin.gradle.internal.KaptExecution.run(KaptWithoutKotlincTask.kt:227)
        at org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction.execute(KaptWithoutKotlincTask.kt:193)
        at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
        at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
        at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
        at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:97)
        at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
        at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
        at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
        at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
        at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$2(DefaultWorkerExecutor.java:206)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:214)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:131)
        ... 3 more
Caused by: org.jetbrains.kotlin.kapt3.base.util.KaptBaseError: Error while annotation processing
        at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing(annotationProcessing.kt:121)
        at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing$default(annotationProcessing.kt:31)
        at org.jetbrains.kotlin.kapt3.base.Kapt.kapt(Kapt.kt:45)
        ... 31 more


* Get more help at https://help.gradle.org

BUILD FAILED in 952ms
2 actionable tasks: 1 executed, 1 up-to-date

What am I doing wrong?

Namespace generated inner classes

If you have two components with the same name with different parent classes, they generate conflicting classes.

class Foo1 {
  @Component abstract class MyComponent()
}
class Foo2 {
  @Component abstract class MyComponent()
}

both generates InjectMyComponent() which causes a compile error.

Implement abstract functions in components

Can kotlin-inject implement abstract functions in components, just the way how abstract values are implemented? I wanna expose some factory lambdas from my component but using them as vals is a bit inconvenient from usage sites because kotlin doesn't allow named arguments for lambdas.

Here's what I'm currently doing:

@Component
abstract class SomeComponent) {
  protected abstract val presenterFactory: (navigator: Navigator) -> SomePresenter

  fun presenter(navigator: Navigator): SomePresenter {
    return presenterFactory(navigator)
  }
}

can this be reduced to:

@Component
abstract class SomeComponent) {
  abstract fun presenter(navigator: Navigator): SomePresenter
}

Look at build performance

Not much though has been put into build performance, so there's def some low-hanging fruit here. Should profile and improve hot paths. Probably mostly unnecessary lookups/lack of caching.

Proposal: make assisted arguments explicit

kotlin-inject's way of injecting dependencies with assisted arguments is really clever but I've been thinking it's very implicit and hidden for readers. Thoughts on making it explicit in some manner? Using dagger-like @Assisted annotation is one option, but I'm not sure if you'd want that. It'll also remove the restriction of keeping assisted arguments at the end of a constructor, which is another hidden restriction.

Allow recusive dependencies with lazy/functions

Right now this stackoverflows but it should be able to be resolved:

class LazyCycleFoo(val bar: LazyCycleBar)
class LazyCycleBar(val foo: Lazy<LazyCycleFoo>)

@Component abstract class LazyCycleComponent {
    abstract val bar: LazyCycleBar

    @Provides fun bar(foo: Lazy<LazyCycleFoo>) = LazyCycleBar(foo)

    @Provides fun foo(bar: LazyCycleBar) = LazyCycleFoo(bar)
}

should generate:

class InjectLazyCycleComponent : LazyCycleComponent() {
    override val bar: LazyCycleBar
        get() {
            lateinit var bar: LazyCycleBar
            return bar(lazy { foo(bar) }).also { bar = it }
        }
}

Leak when child dependency is provided to a parent dependency that has a longer lifecycle

If a child dependency is provided to a parent dependency, and that parent has a longer lifecycle than the child (e.g. the parent is scoped to an Android application, and the child is scoped to an Android activity), then the child dependency can leak (in the example below, the activity context is leaked):

@Inject
class ResolverWrapper(val resolver: ContentResolver)

@Singleton
@Component abstract class ParentComponent {
  @Provides @Singleton fun providesContentResolver(
    context: Context
  ): ContentResolver = context.contentResolver
}

@Component abstract class ChildComponent(
  @Component val parent: ParentComponent,
  @get:Provides val activityContext: Context
) {
  abstract val wrapper: ResolverWrapper
}

Properly handle nullable/platform types.

Kotlin has 3 versions of a type: non-nullable, nullable, and platform, represented as:
Foo, Foo?, and Foo! respectively. We need to handle these correctly and consistently across backends. The current behavior is:

  • kapt: all 3 types match with each other. This 'works' sometimes but generates incorrect code when it matches Foo? to Foo.
  • ksp: all 3 types are distinct. This works, but can be surprising in the case of platform times where @Provides fun foo() = javaMethodThatReturnsFoo() won't match Foo.

To say another way, which ones of these should compile, and which ones should complain about a missing binding?

@Component MyComponent {
  abstract val foo: Foo
  @Provides fun foo() = javaMethodThatReturnsFoo()
}
@Component MyComponent {
  abstract val foo: Foo?
  @Provides fun foo() = javaMethodThatReturnsFoo()
}
@Component MyComponent {
  abstract val foo: Foo?
  @Provides fun foo(): Foo = javaMethodThatReturnsFoo()
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.