{"id":19646576,"url":"https://github.com/amzn/kotlin-inject-anvil","last_synced_at":"2025-05-16T10:06:27.433Z","repository":{"id":255762035,"uuid":"844818788","full_name":"amzn/kotlin-inject-anvil","owner":"amzn","description":"Extensions for the kotlin-inject dependency injection framework","archived":false,"fork":false,"pushed_at":"2025-05-08T21:11:42.000Z","size":416,"stargazers_count":341,"open_issues_count":5,"forks_count":14,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-08T22:23:50.291Z","etag":null,"topics":["dependency-injection","kotlin","kotlin-multiplatform"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amzn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-08-20T03:08:04.000Z","updated_at":"2025-05-08T21:11:45.000Z","dependencies_parsed_at":"2024-10-24T20:16:03.133Z","dependency_job_id":"92c6f1a4-87cc-4a0d-a131-e16052fdbf38","html_url":"https://github.com/amzn/kotlin-inject-anvil","commit_stats":null,"previous_names":["amzn/kotlin-inject-anvil"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amzn%2Fkotlin-inject-anvil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amzn%2Fkotlin-inject-anvil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amzn%2Fkotlin-inject-anvil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amzn%2Fkotlin-inject-anvil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amzn","download_url":"https://codeload.github.com/amzn/kotlin-inject-anvil/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254509476,"owners_count":22082891,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","kotlin","kotlin-multiplatform"],"created_at":"2024-11-11T14:39:18.415Z","updated_at":"2025-05-16T10:06:27.405Z","avatar_url":"https://github.com/amzn.png","language":"Kotlin","funding_links":[],"categories":["IoC"],"sub_categories":["语音合成"],"readme":"# kotlin-inject-anvil\n\n[![Maven Central](https://img.shields.io/maven-central/v/software.amazon.lastmile.kotlin.inject.anvil/compiler.svg?label=Maven%20Central)](https://central.sonatype.com/search?smo=true\u0026namespace=software.amazon.lastmile.kotlin.inject.anvil)\n[![CI](https://github.com/amzn/kotlin-inject-anvil/workflows/CI/badge.svg)](https://github.com/amzn/kotlin-inject-anvil/actions?query=branch%3Amain)\n[![Slack channel](https://img.shields.io/badge/chat-slack-blue.svg?logo=slack)](https://kotlinlang.slack.com/messages/kotlin-inject/)\n\n[kotlin-inject](https://github.com/evant/kotlin-inject) is a compile-time dependency injection\nframework for Kotlin Multiplatform similar to Dagger 2 for Java.\n[Anvil](https://github.com/square/anvil) extends Dagger 2 to simplify dependency injection.\n\nThis project provides a similar feature set for the `kotlin-inject` framework. The extensions provided\nby `kotlin-inject-anvil` allow you to contribute and automatically merge component interfaces without explicit\nreferences in code.\n\n```kotlin\n@ContributesTo(AppScope::class)\ninterface AppIdComponent {\n    @Provides\n    fun provideAppId(): String = \"demo app\"\n}\n\n@Inject\n@SingleIn(AppScope::class)\n@ContributesBinding(AppScope::class)\nclass RealAuthenticator : Authenticator\n\n// The final kotlin-inject component.\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\ninterface AppComponent\n\n// Instantiate the component at runtime.\nval component = AppComponent::class.create()\n```\nFrom the above example code snippet:\n\n* `AppIdComponent` will be made a super type of the final component and the\nprovider method is known to the object graph, so you can inject and use AppId anywhere.\n* A binding for `RealAuthenticator` will be generated and the type `Authenticator` can safely be injected anywhere.\n* Note that neither `AppIdComponent` nor `RealAuthenticator` need to be referenced anywhere else in your code.\n\n## Setup\n\nThe project comes with a KSP plugin and a runtime module:\n```groovy\ndependencies {\n    kspCommonMainMetadata \"software.amazon.lastmile.kotlin.inject.anvil:compiler:$version\"\n    commonMainImplementation \"software.amazon.lastmile.kotlin.inject.anvil:runtime:$version\"\n\n    // Optional module, but strongly suggested to import. It contains the\n    // @SingleIn scope and @ForScope qualifier annotation together with the\n    // AppScope::class marker.\n    commonMainImplementation \"software.amazon.lastmile.kotlin.inject.anvil:runtime-optional:$version\"\n}\n```\n\nYou should setup kotlin-inject as described in the [official docs](https://github.com/evant/kotlin-inject).\nFor details how to setup KSP itself for multiplatform projects, see the\n[official documentation](https://kotlinlang.org/docs/ksp-multiplatform.html).\n\n#### Snapshot builds\n\nTo import snapshot builds use following repository:\n```groovy\nmaven {\n    url = 'https://aws.oss.sonatype.org/content/repositories/snapshots/'\n}\n```\n\n## Usage\n\n### Contributions\n\n#### `@ContributesTo`\n\nComponent interfaces can be contributed using the `@ContributesTo` annotation:\n```kotlin\n@ContributesTo(AppScope::class)\ninterface AppIdComponent {\n    @Provides\n    fun provideAppId(): String = \"demo app\"\n}\n```\nThe scope `AppScope::class` tells `kotlin-inject-anvil` in which component to merge this\ninterface.\n\n#### `@ContributesBinding`\n\n`kotlin-inject` requires you to write\n[binding / provider methods](https://github.com/evant/kotlin-inject#usage) in order to provide a\ntype in the object graph. Imagine this API:\n```kotlin\ninterface Authenticator\n\nclass RealAuthenticator : Authenticator\n```\nWhenever you inject `Authenticator` the expectation is to receive an instance of\n`RealAuthenticator`. With vanilla `kotlin-inject` you can achieve this with a provider\nmethod:\n```kotlin\n@Inject\n@SingleIn(AppScope::class)\nclass RealAuthenticator : Authenticator\n\n@ContributesTo(AppScope::class)\ninterface AuthenticatorComponent {\n    @Provides\n    fun provideAuthenticator(authenticator: RealAuthenticator): Authenticator = authenticator\n}\n```\nNote that `@ContributesTo` is leveraged to automatically add the interface to the final component.\n\nHowever, this is still too much code and can be simplified further with `@ContributesBinding`:\n```kotlin\n@Inject\n@SingleIn(AppScope::class)\n@ContributesBinding(AppScope::class)\nclass RealAuthenticator : Authenticator\n```\n`@ContributesBinding` will generate a provider method similar to the one above and automatically\nadd it to the final component.\n\n##### Multi-bindings\n\n`@ContributesBinding` supports `Set` multi-bindings via its `multibinding` parameter.\n\n```kotlin\n@Inject\n@SingleIn(AppScope::class)\n@ContributesBinding(AppScope::class, multibinding = true)\nclass LoggingInterceptor : Interceptor\n\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\nabstract class AppComponent {\n    // Will be contributed to this set multi-binding.\n    abstract val interceptors: Set\u003cInterceptor\u003e\n}\n```\n\n#### `@ContributesSubcomponent`\n\nThe `@ContributesSubcomponent` annotation allows you to define a subcomponent in any Gradle module,\nbut the final `@Component` will be generated when the parent component is merged.\n```kotlin\n@ContributesSubcomponent(LoggedInScope::class)\n@SingleIn(LoggedInScope::class)\ninterface RendererComponent {\n\n    @ContributesSubcomponent.Factory(AppScope::class)\n    interface Factory {\n        fun createRendererComponent(): RendererComponent\n    }\n}\n```\nFor more details on usage of the annotation and behavior\n[see the documentation](runtime/src/commonMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/ContributesSubcomponent.kt).\n\n#### Assisted injection\n\nWhen using the `@ContributesBinding` annotation in combination with the kotlin-inject `@Assisted`\nannotation, then you can inject the factory lambda with the base type as return type:\n```kotlin\ninterface Authenticator {\n    fun authenticate(): Result\n}\n\n@Inject\n@ContributesBinding(AppScope::class)\nclass RealAuthenticator(\n    @Assisted val credentials: Credentials,\n): Authenticator {\n    override fun authenticate(): Result = sendAuthenticationRequest(credentials)\n}\n\n@Inject\nclass LoginScreen(val authenticatorFactory: (Credentials) -\u003e Authenticator) {\n    fun login(credentials: Credentials) {\n        // Note that this lambda returns Authenticator and NOT RealAuthenticator.\n        val authenticator = authenticatorFactory(credentials)\n        authenticator.authenticate()\n    }\n}\n```\n\nNote that the above example binds the factory as a lambda because of how\n[assisted injection](https://github.com/evant/kotlin-inject?tab=readme-ov-file#function-support--assisted-injection)\nworks with kotlin-inject. If you wish to have a more strongly typed interface bound to create the\ndependency, then you can create an explicit Factory interface and bind that in a default\nimplementation. A common pattern looks like this:\n\n```kotlin\ninterface AuthenticatorFactory {\n    fun create(credentials: Credentials): Authenticator\n}\n\n@Inject\n@ContributesBinding(AppScope::class)\nclass RealAuthenticatorFactory(\n    private val realAuthenticatorFactory: (Credentials) -\u003e RealAuthenticator,\n) : AuthenticatorFactory {\n    override fun create(credentials: Credentials): Authenticator = realAuthenticatorFactory(credentials)\n}\n```\n\n### Merging\n\nWith `kotlin-inject`, components are defined similar to the one below in order to instantiate your\nobject graph at runtime:\n```kotlin\n@Component\n@SingleIn(AppScope::class)\ninterface AppComponent\n```\nIn order to pick up all contributions, you must change the `@Component` annotation to\n`@MergeComponent`:\n```kotlin\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\ninterface AppComponent\n```\nThis will generate a new component class with the original `@Component` annotation and merge all\ncontributions to the scope `AppScope`.\n\nTo instantiate the component at runtime, call the generated `create()` function:\n```kotlin\nval component = AppComponent::class.create()\n```\n\n#### Parameters\n\nParameters are supported the same way as with `kotlin-inject`:\n```kotlin\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\nabstract class AppComponent(\n    @get:Provides val userId: String,\n)\n\nval component = AppComponent::class.create(\"userId\")\n```\n\n#### Kotlin Multiplatform\n\nWith Kotlin Multiplatform there is a high chance that the generated code cannot be referenced\nfrom common Kotlin code or from common platform code like `iosMain`. This is due to how\n[common source folders are separated from platform source folders](https://kotlinlang.org/docs/whatsnew20.html#separation-of-common-and-platform-sources-during-compilation).\nFor more details and recommendations setting up kotlin-inject in Kotlin Multiplatform projects\nsee the [official guide](https://github.com/evant/kotlin-inject/blob/main/docs/multiplatform.md).\n\nTo address this issue, you can define an `expect fun` in the common source code next to\ncomponent class itself. The `actual fun` will be generated and create the component. The\nfunction must be annotated with `@MergeComponent.CreateComponent`. It's optional to have a\nreceiver type of `KClass` with your component type as argument. The number of parameters\nmust match the arguments of your component and the return type must be your component, e.g.\nyour component in common code could be declared as:\n```kotlin\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\nabstract class AppComponent(\n    @get:Provides userId: String,\n)\n\n// Create this function next to your component class. The actual function will be generated.\n@CreateComponent\nexpect fun create(appId: String): AppComponent\n\n// Or with receiver type:\n@CreateComponent\nexpect fun KClass\u003cAppComponent\u003e.create(appId: String): AppComponent\n```\nThe generated `actual fun` will be generated and will look like this:\n```kotlin\nactual fun create(appId: String): AppComponent {\n    return KotlinInjectAppComponent::class.create(appId)\n}\n```\n\n### Scopes\n\nThe plugin builds a connection between contributions and merged components through the scope\nparameters. Scope classes are only markers and have no further meaning besides building a\nconnection between contributions and merging them. The class `AppScope` from the sample could\nlook like this:\n```kotlin\nobject AppScope\n```\n\nScope classes are independent of the `kotlin-inject`\n[scopes](https://github.com/evant/kotlin-inject#scopes). It's still necessary to set a scope for\nthe `kotlin-inject` components or to make instances a singleton in a scope, e.g.\n```kotlin\n@Inject\n@SingleIn(AppScope::class) // scope for kotlin-inject\n@ContributesBinding(AppScope::class)\nclass RealAuthenticator : Authenticator\n\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class) // scope for kotlin-inject\ninterface AppComponent\n```\n\n`kotlin-inject-anvil` provides the\n[`@SingleIn` scope annotation](runtime-optional/src/commonMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/SingleIn.kt)\noptionally by importing following module. We strongly recommend to use the annotation for\nconsistency.\n```groovy\ndependencies {\n    commonMainImplementation \"software.amazon.lastmile.kotlin.inject.anvil:runtime-optional:$version\"\n}\n```\n\n## Sample\n\nA [sample project](sample) for Android and iOS is available.\n\n## Talk\n\nThe idea and more background about this library is covered in this\n[public talk](https://ralf-wondratschek.com/presentation/extending-kotlin-inject-nyc.html).\n\n## Advanced options\n\n### Custom symbol processors\n\n`kotlin-inject-anvil` is extensible and you can create your own annotations and KSP symbol\nprocessors. In the generated code you can reference annotations from `kotlin-inject-anvil` itself\nand build logic on top of them.\n\nFor example, assume this is your annotation:\n```kotlin\n@Target(CLASS)\n@ContributingAnnotation // see below for details\nannotation class MyCustomAnnotation\n```\n\nYour custom KSP symbol processor uses this annotation as trigger and generates following code:\n```kotlin\n@ContributesTo(AppScope::class)\ninterface MyCustomComponent {\n    @Provides\n    fun provideMyCustomType(): MyCustomType = ...\n}\n```\nThis generated component interface `MyCustomComponent` will be picked up by `kotlin-inject-anvil's`\nsymbol processors and contributed to the `AppScope` due to the `@ContributesTo` annotation.\n\n**Custom annotations and symbol processors are very powerful and allow you to adjust\n`kotlin-inject-anvil` to your needs and your codebase.**\n\nThere are two ways to indicate these to `kotlin-inject-anvil`. This is important for incremental\ncompilation and multi-round support.\n\n1. **This is the preferred option**: Annotate your annotation with the `@ContributingAnnotation`\n    marker and run `kotlin-inject-anvil`'s compiler over the project the annotation is hosted in.\n    Adding the compiler as described in the [the setup](#setup) is important, otherwise the\n    `@ContributingAnnotation` has no effect. With this the annotation is understood as a\n    contributing annotation in all downstream usages of this annotation.\n    ```kotlin\n    @ContributingAnnotation // \u003c--- add this!\n    @Target(CLASS)\n    annotation class MyCustomAnnotation\n    ```\n2. Alternatively, if you don't control the annotation or otherwise cannot use option 1, you can\n    specify custom annotations via the `kotlin-inject-anvil-contributing-annotations` KSP option.\n    This option value is a colon-delimited string whose values are the canonical class names of\n    your custom annotations.\n    ```kotlin\n    ksp {\n      arg(\"kotlin-inject-anvil-contributing-annotations\", \"com.example.MyCustomAnnotation\")\n    }\n    ```\n\n### Disabling processors\n\nIn some occasions the behavior of certain built-in symbol processors of `kotlin-inject-anvil`\ndoesn't meet expectations or should be changed. The recommendation in this case is to disable\nthe built-in processors and create your own. A processor can be disabled through KSP options, e.g.\n\n```groovy\nksp {\n    arg(\"software.amazon.lastmile.kotlin.inject.anvil.processor.ContributesBindingProcessor\", \"disabled\")\n}\n```\n\nThe key of the option must match the fully qualified name of the symbol processor and the value\nmust be `disabled`. All other values will keep the processor enabled. All built-in symbol\nprocessors are part of\n[this package](compiler/src/main/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor).\n\n## Security\n\nSee [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.\n\n## License\n\nThis project is licensed under the Apache-2.0 License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famzn%2Fkotlin-inject-anvil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famzn%2Fkotlin-inject-anvil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famzn%2Fkotlin-inject-anvil/lists"}