evant / kotlin-inject

Dependency injection lib for kotlin
Apache License 2.0
1.14k stars 51 forks source link

Can't inject provided dependency into java class #401

Closed sergeshustoff closed 3 days ago

sergeshustoff commented 2 weeks ago

Problem: when there is a java class in dependency graph we have to create @Provides function in component in order to inject it - having @Inject annotation on that's java class' constructor doesn't work.

I'm not sure if it's ksp limitation or not, will need to research that...

evant commented 1 week ago

It should work in theory, there's even a test for it here, https://github.com/evant/kotlin-inject/blob/main/integration-tests/common-jvm/src/test/kotlin/me/tatarka/inject/test/JavaTest.kt, but it's possible there may be an issue with it, any chance you can create a sample the reproduces?

sergeshustoff commented 1 week ago

Looks like my complicated setup breaks in some corner case. I'll try find a simple case to reproduce it

sergeshustoff commented 1 week ago

failing_provided_injection_in_java_class.patch Ok, here is a patch to make test fail. Looks like anything that component @Provides doesn't get injected into java constructor

// in JavaTest
@Component
abstract class JavaInjectConstructorComponent {
    abstract val foo: JavaFoo

    @Provides
    fun Foo.bind(): IFoo = this

    abstract val foo2: JavaJavaXFoo
}

// in java file
public class JavaFoo {

    private final IFoo foo;

    @Inject
    public JavaFoo(IFoo foo) {
        this.foo = foo;
    }

    public IFoo getFoo() {
        return foo;
    }
}
sergeshustoff commented 1 week ago

Adding org.jetbrains.annotations.NotNull annotation (androidx annotation works too) to java class constructor parameters fixes the problem.

Seems that the right way to fix it would be to show message similar to one in #110 for dependency types

sergeshustoff commented 1 week ago

Though there is a strange difference in behaviour for things provided by inject constructor and provided explicidly

                // java code
                public class JavaIFoo  {

                    private final Foo foo;

                    @Inject
                    public JavaIFoo(Foo foo) {
                        this.foo = foo;
                    }

                    public Foo getFoo() {
                        return foo;
                    }
                }

                // kotlin code
                @Inject 
                class Foo

                @Component
                abstract class JavaProvidedDependencyComponent {
                    abstract val javaFoo: JavaIFoo

                // @Provides fun foo() = Foo
                }

It works like this, but if @Inject is commented on Foo and @Provides is uncommented - it stops working. I'm not sure anymore what the behavioud should be