evant / kotlin-inject Goto Github PK
View Code? Open in Web Editor NEWDependency injection lib for kotlin
License: Apache License 2.0
Dependency injection lib for kotlin
License: Apache License 2.0
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.
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}
@Inject object Foo
can be useful if you want to be able to change it from a singleton to an instance later.
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:
It seems that the configuration suggested in #1 does not work with kotlin 1.6.2(0/1) anymore. Any pointers on getting this working again?
@Component interface MyComponent {
fun foo(): Foo
}
expected:
class InjectMyComponent : MyComponent {
override fun foo(): Foo = Foo()
}
got:
class InjectMyComponent : MyComponent {
override val foo: Foo get() = Foo()
}
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.
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:
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.
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.
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).
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.
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.
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
}
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 = ...
}
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
}
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
}
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() = ... }
Need to validate the graph (ex: detect cycles) to give better information on failure cases.
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
}
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:
Foo?
to Foo
.@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()
}
@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 }
}
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?
You can write a class name with backticks like '`My Weird Class`'. This works on ksp but not kapt. Is there a way to fix this?
It would be great if we could have singletons and lazy singleton support
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
.
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.
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.
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
}
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)
If you have
@Component abstract class MyComponent(val foo: String = "Foo")
then it should generate
fun KClass<MyComponent>.create(foo: String = "Foo") = InjectMyComponent(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() })
}
Migrate to a kotlin compiler plugin to support multiplatform
module is just too confusing in a multi-module project world.
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.
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.
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.
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.
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.
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)
This would make it easier to migrate existing code.
Need to improve error handling/reporting to better pinpoint what needs to be fixed.
Right now scopes are ignored on inject functions, they should work the same as injecting into a class.
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
}
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 }
}
}
Hi, thanks for this neat injection framework. Is it still under active development?
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
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.
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
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
...
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.