{"id":18682852,"url":"https://github.com/yandex/yatagan","last_synced_at":"2025-04-12T23:29:45.895Z","repository":{"id":64979636,"uuid":"570094802","full_name":"yandex/yatagan","owner":"yandex","description":"Dependency Injection framework based on Google's Dagger2 API, optimized for fast builds and for managing large graphs with optional dependencies","archived":false,"fork":false,"pushed_at":"2025-04-12T09:58:51.000Z","size":3020,"stargazers_count":237,"open_issues_count":12,"forks_count":11,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-04-12T10:39:56.914Z","etag":null,"topics":["android","dagger2","dependency-injection","kapt","kotlin","ksp","reflection"],"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/yandex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-24T10:24:47.000Z","updated_at":"2025-04-12T09:58:55.000Z","dependencies_parsed_at":"2023-09-28T02:15:35.954Z","dependency_job_id":"45b10ceb-09ea-405b-ab10-9befba1ae5b2","html_url":"https://github.com/yandex/yatagan","commit_stats":{"total_commits":520,"total_committers":8,"mean_commits":65.0,"dds":"0.13076923076923075","last_synced_commit":"d0d7b823711c7f5a48cfd76d673ad348407fd904"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yandex%2Fyatagan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yandex%2Fyatagan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yandex%2Fyatagan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yandex%2Fyatagan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yandex","download_url":"https://codeload.github.com/yandex/yatagan/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248646991,"owners_count":21139079,"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":["android","dagger2","dependency-injection","kapt","kotlin","ksp","reflection"],"created_at":"2024-11-07T10:13:00.741Z","updated_at":"2025-04-12T23:29:45.866Z","avatar_url":"https://github.com/yandex.png","language":"Kotlin","funding_links":[],"categories":["IoC"],"sub_categories":[],"readme":"# Yatagan\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.yandex.yatagan/api-compiled/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.yandex.yatagan/api-compiled)\n[![CI](https://github.com/yandex/yatagan/actions/workflows/main.yaml/badge.svg)](https://github.com/yandex/yatagan/actions/workflows/main.yaml)\n[![codecov](https://codecov.io/gh/yandex/yatagan/graph/badge.svg?token=XW9AVMQWM0)](https://codecov.io/gh/yandex/yatagan)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\nYatagan is a fast **Dependency Injection framework** based on Google's [Dagger2][D2] API.\n\nYatagan is optimized for fast builds and supports managing large dynamic dependency graphs by introducing\n[_Conditions_ and _Variants_](#added-apis).\nIt's primary goal is to **improve build speed in large complex projects, which already use Dagger**.\nThough it might be useful for others.\n\nAll core Dagger API is supported with little changes.\nYet dagger-android, Hilt and a couple of less popular features are not supported.\nSee comparative [API reference](#yatagan-vs-dagger-api-reference) for full info.\n\nYatagan can work in multiple modes (use different _backends_):\n\n- With code generation\n    - APT/KAPT - classic mode.\n    - KSP - leverages new Google [KSP][KSP] framework. Experimental, see [KSP support status](#ksp-support).\n- Via runtime Java reflection - a backend designed for fast local debug builds, see\n  specific [notes](#reflection-support).\n\nAll backends are designed to be working _identically_, so one can easily switch backends depending on the\nworking environment and requirements. Any inconsistencies between backends ought to be reported and fixed.\n\n## Motivation\n\nOne can consider migrating to Yatagan from vanilla Dagger if at least one of the points is true for their project:\n\n1. The project uses Dagger and has **build performance issues** because of it.\n2. The project needs/extensively uses **dynamic optional dependencies** in its DI graphs.\n\nYatagan tackles both of these issues.\n\nRead more and dive into technical details in the Medium [article][MEDIUM]. \n\n### Performance\n\nAs of the first point, performance gains can vary per project due to specific details and Yatagan usage configuration.\nYatagan allows clients to **apply processing to fewer project modules** in comparison to Dagger.\nYatagan processing should only be applied to project modules, that contain _root components_,\nand shouldn't be applied at all in case of reflection mode usage.\n\nThus, a project will have the biggest performance gain from using Yatagan, if the majority of the project modules\nhave only one annotation processor - Dagger. Then upon migration to Yatagan project modules without root components can\nhave kapt completely switched off with it remaining in root-component modules. Furthermore, root-component modules can\nalso become kapt-free with Reflection mode.\nIn a good scenario Yatagan can make incremental builds **up to two times faster**.\n\nIf other annotation processors besides Dagger are used throughout the project in KAPT mode,\nthen performance gain from using Yatagan will be lower.\nOne can try and reorganise the project to limit other annotation processors appliance to small modules or\nuse them in KSP mode if supported.\nReflection mode is also supported for some frameworks that feature code generation.\nIt can be enabled in debug builds if this allows to eliminate KAPT from the project module.\n\nThe general idea is to remove KAPT from as many modules as possible, with large modules yielding more profit, so\nfeel free to experiment with what Yatagan offers for this task.\n\nIn the worst case scenario, where using Yatagan doesn't remove KAPT from any of the modules, profits can still be around\n~ 10% due to Yatagan optimizations.\n\n### Runtime conditions\n\nThe second point can be clarified by the following code snippet:\n\n```kotlin\n@Singleton\nclass MyClass @Inject constructor(\n    /**\n     * Dynamic optional dependency, that is present/absent in DI-graph based on declared runtime condition.\n     */\n    val myOptionalDependency: Optional\u003cMyClassUnderRuntimeCondition\u003e,\n)\n```\n\nThis is one of the approaches that can be taken into coding optional dependencies.\nNaturally, such thing can be written with Dagger's `@Provides Optional\u003cMyClassUnderRuntimeCondition\u003e provide(..) { .. }`\nyet such code is arguably difficult to maintain, verbose,\nand scales poorly with the growing number of conditions and classes under them.\n\nYatagan solves this by introducing first-class runtime condition support with compile-time condition validation.\nSee [_Conditions_/_Variants_ APIs](#added-apis).\n\n## Usage (Gradle)\n\nCode generation dependency is only required for project modules, that contain root component declarations\n(`@Component(isRoot = true/* default */)`). For modules, that contain classes with `@Inject`, `@Provides`, etc..\nno dependencies but \"api\" ones are required.\nThis is different for Dagger, which requires you to apply annotation processing in every module with DI code to\nallow Gradle incremental compilation/annotation processing to work correctly.\n\nYatagan can be used in various configurations. Choose one, that suits your project.\nSee the following Gradle buildscript usage snippets (code is assumed to be inside a `dependencies {}` block).\n\nFor kotlin-only/mixed project using **kapt**:\n\n```kotlin\n// Ensure `kotlin-kapt` plugin is applied\napi(\"com.yandex.yatagan:api-compiled:${yataganVer}\")\n// kapt is slow but generally reliable for mixed projects.\nkapt(\"com.yandex.yatagan:processor-jap:${yataganVer}\")\n```\n\nFor kotlin-only/mixed project using **KSP** (use with caution for Java code):\n([How to](https://kotlinlang.org/docs/ksp-quickstart.html#use-your-own-processor-in-a-project) apply KSP plugin)\n\n```kotlin\n// Ensure `com.google.devtools.ksp` plugin is applied\napi(\"com.yandex.yatagan:api-compiled:${yataganVer}\")\n// KSP implementation is unstable. Works best for pure-Kotlin projects.\nksp(\"com.yandex.yatagan:processor-ksp:${yataganVer}\")\n```\n\nTo dramatically speed up build one can use **runtime reflection** instead of codegen:\n\n```kotlin\n// No codegen dependency is required, the reflection engine comes as a dependency of the `api-dynamic` artifact.\napi(\"com.yandex.yatagan:api-dynamic:${yataganVer}\")\n```\n\nFor **java-only** project:\n\n```kotlin\napi(\"com.yandex.yatagan:api-compiled:${yataganVer}\")\n// best codegen backend for Java-only, no need to use kapt/ksp.\nannotationProcessor(\"com.yandex.yatagan:processor-jap:${yataganVer}\")\n```\n\n**Android** projects are advised to follow the same usage guidelines,\nthough make sure to read the [notes](#android) on reflection on Android.\nAn example of a recommended way to use Yatagan for Android projects:\n\n```kotlin\n// Use reflection in debug builds.\ndebugApi(\"com.yandex.yatagan:api-dynamic:${yataganVer}\")\n\n// Use codegen in releases\nreleaseApi(\"com.yandex.yatagan:api-compiled:${yataganVer}\")\nif (kspEnabled) {\n    kspRelease(\"com.yandex.yatagan:processor-ksp:${yataganVer}\")\n} else {\n    kaptRelease(\"com.yandex.yatagan:processor-jap:${yataganVer}\")\n}\n```\n\nOne may want to create a shared library that exposes a piece of Yatagan graph, \nyet doesn't create any root components itself. \nIn this case, the library can depend on `com.yandex.yatagan:api-public`,\nwhich provides pure Yatagan API and no backend-specific entry-points.\n\n## Backends\n\n### KAPT/APT\nAPT or KAPT (Yatagan qualifies the artifacts with `jap`, **j**ava **a**nnotation **p**rocessing) is a legacy backend,\nthough it's stable and can be reliably used by default.\n\n### KSP support\n\nYatagan supports KSP in experimental mode.\nThis is mostly due to the fact that Yatagan operates in terms of Java type system\nand is very sensitive to type equality. In Kotlin, `Collection` and `MutableCollection` are different types, though in\nJava it's the same type. From the other hand, Kotlin's `Int` is represented in Java as `int` and `Integer`.\nChoosing Java types to maintain semantic compatibility with Dagger, Yatagan converts Kotlin types into Java ones.\nKSP API related to JVM is explicitly marked as `@KspExperimental`, and practice shows KSP support for modeling Java\ncode is at least inconsistent.\n\nThus, KSP can be adopted for Kotlin-only projects, or projects whose DI-code is mostly Kotlin.\nAdditional care should be taken with Java projects.\n\nAlso, KSP strictly depends on Kotlin compiler version, used in your project, so using KSP may force you to keep updating\nKotlin compiler version frequently.\n\n### Reflection support\n\nReflection support is considered stable in Yatagan. There's already a very similar project for the vanilla Dagger -\n[dagger-reflect][DR]. However, Reflection mode in Yatagan has fist-class support and guaranteed to behave the same way,\nas generated implementation would.\nIf a new feature is implemented in Yatagan, reflection automatically works with it.\n\nTechnically, reflection mode can be used in production, though it's advised not to do so, as code generation naturally\nproduces much more performant code. Also, reflection mode is broken by code minifiers, such as Proguard or R8.\n\nRead more in [reflection backend specific notes](rt/README.md).\n\n#### Android\n\nReflection backend fully supports Android applications starting with `minSdk = 24`.\nBelow that, static methods in interfaces are not directly supported in Android and have to be \"desugared\" by AGP.\nYatagan Reflection doesn't currently read such desugared methods as they have no stable ABI and reading them will bring\nperformance penalties.\nSo consider using `minSdk = 24` at least for debug build type to safely use Yatagan with Reflection.\n\n## Yatagan vs Dagger API reference\n\n| Dagger2 API (`dagger.**`)              | Status in Yatagan | Notes                                         |\n|----------------------------------------|-------------------|-----------------------------------------------|\n| `@Component`                           | 🟢 as is          |                                               |\n| `@Component.Builder`                   | 🟢 as is          | supports factory method as well               |\n| `@Component.Factory`                   | 🟡 converged      | functionality merged into `@Builder`          |\n| `@Subcomponent`                        | 🟡 converged      | replaced by `Component(isRoot = false)`       |\n| `@Subcomponent.{Builder/Factory}`      | 🟡 converged      | replaced by `Component.Builder`               |\n| `Lazy`                                 | 🟢 as is          | now also extends `javax.inject.Provider`      |\n| `@Module`                              | 🟢 as is          |                                               |\n| `@Binds`                               | 🟡 tweaked        | can bind zero/multiple alternatives           |\n| `@BindsInstance`                       | 🟢 as is          |                                               |\n| `@Provides`                            | 🟢 as is          | supports conditional provision                |\n| `@BindsOptionalOf`                     | 🟡 replaced       | replaced with [Variants API](#added-apis)     |\n| `@Reusable`                            | 🟢 as is          |                                               |\n| `MembersInjector`                      | 🔴 unsupported    |                                               |\n| `@MapKey`                              | 🟡 renamed*       | `IntoMap.Key`, *`unwrap=false` is unsupported |\n| `@multibindings.IntoSet`               | 🟢 as is          |                                               |\n| `@multibindings.ElementsIntoSet`       | 🟡 converged      | `IntoSet(flatten = true)`                     |\n| `@multibindings.Multibinds`            | 🟢 as is          |                                               |\n| `@multibindings.IntoMap`               | 🟢 as is          |                                               |\n| `@multibindings.{Int,Class,String}Key` | 🟢 as is          |                                               |\n| `@multibindings.LongKey`               | 🔴 removed        | can be declared manually if required          |\n| `assisted.*`                           | 🟢 as is          |                                               |\n| `producers.*`                          | 🔴 unsupported    |                                               |\n| `android.*`                            | 🔴 unsupported    |                                               |\n| `grpc.*`                               | 🔴 unsupported    |                                               |\n| `hilt.**`                              | 🔴 unsupported    |                                               |\n| `spi.*`                                | 🟡 replaced       | Yatagan has its own model for [SPI](#plugins) |\n\nOther behavioral changes:\n\n- `@Binds` can't be scoped (scope rebind is not allowed). Use scope on the implementation.\n  Also, Yatagan supports declaring multiple scopes on bindings, \n  so the binding is compatible with _every_ scope declared. Dagger only allowed doing so for components.\n\n- Yatagan requires components, builders, assisted inject factories to be declared as interfaces.\n  Abstract classes are forbidden. This is due to the limitations of RT mode. Dagger-reflect has the same limitation.\n\n- If codegen is used, generated component implementations are not named `Dagger\u003ccomponent-name\u003e`,\n  their names are mangled, and the access should be made via \n  `Yatagan.builder()`/`Yatagan.create()` entry-point invocations.\n  This is made to support reflection backend.\n  Actual `Yatagan` implementations are provided within `com.yandex.yatagan:api-dynamic` and \n  `com.yandex.yatagan:api-compiled` artifacts. \n\n- Yatagan does not support `@Nullable` provisions. If a binding returns `null`, or a `@BindsInstance` is supplied with\n  `null`, an error will be thrown at run-time. Currently, no compile-time validation is done in the matter.\n\n- Automatic component factory/builder generation is not supported - an explicit one must be written if required.\n\n- Member inject in Kotlin code should be used with care:\n  `@Inject lateinit var prop: SomeClass` will work as expected,\n  though `@Inject @Named(\"id\") lateinit var prop: SomeClass` will not - qualifier annotation will go to the *property*\n  instead of *field*, and Yatagan will not be able to see it.\n  In fact vanilla Dagger will also fail to see it in some scenarios, though it tries to do so on the best-effort basis.\n  Yatagan can't read annotations from Kotlin properties, so the following explicit forms should be used instead:\n  `@Inject @field:Named(\"id\") lateinit var prop: SomeClass` to inject directly to the field, or\n  `@set:Inject @set:Named(\"id\") lateinit var prop: SomeClass` to inject via setter.\n\nYatagan was written from scratch, and as major known inconsistencies are documented here,\nthere is a possibility for differences that are overlooked.\nIf you happen to discover one, please report it.\n\n## Migration from Dagger\n\nStrictly speaking, Yatagan and Dagger are _not directly compatible_.\nYatagan uses a separate binary-incompatible set of annotations and helpers\nto give it a degree of freedom to extend and enhance the API.\n\nYet for the majority of cases, as documented in the [api reference][REF],\nannotations and classes differ only in package names, which makes migration from Dagger to Yatagan somewhat trivial.\n\nThe general idea of steps one needs to take to migrate from Yatagan to Dagger:\n\n1. Replace `import dagger\\.multibindings\\.` -\u003e `import com.yandex.yatagan.`\n2. Replace `import dagger\\.assisted\\.` -\u003e `import com.yandex.yatagan.`\n3. Replace `import dagger\\.` -\u003e `import com.yandex.yatagan.`\n4. Replace `@Subcomponent` annotations with `@Component(isRoot = false)` ones.\n5. Replace `@Component.Factory` with `@Component.Builder`.\n6. Get rid of all nullable provisions. Yatagan does not support them.\n7. Replace `DaggerMyComponent.builder()` with `Yatagan.builder(MyComponent.Builder::class.java)` or similar.\n8. Mark all components, that are accessed from multiple threads as `@Component(.., multiThreadAccess = true)`.\n   If you are unsure, if a component is accessed from a single thread, but ideally it should be,\n   you can set up a check with `Yatagan.setThreadAsserter()`.\n9. Run build and fix all remaining inconsistencies (like implicitly included subcomponents, etc..).\n\n## Added APIs\n\nYatagan introduces the following new APIs, that can be utilized to work with **conditional bindings**\n\nThe first one is [`@Condition`](api/public/src/main/kotlin/Condition.kt).\nWith this annotation, one can declare a **runtime condition** that can be evaluated and its value will\ndetermine the presence/absence of a binding under the condition.\n\nTo put a binding under a given condition, one must use [`@Conditional`](api/public/src/main/kotlin/Conditional.kt)\nannotation on a binding or a class with `@Inject`-annotated constructor.\n\nVariant API ideally replaces Dagger's `@BindsOptionalOf` and makes it more powerful.\nIt's very alike to how Android works with flavors and dimensions, only here we can declare components having\nsuch flavors and include/exclude bindings based on them.\nTo use that, one can employ `@Conditional(.., onlyIn = ...)` and `@Component(variant = ...)` attributes. \n\nFeel free to read a small tutorial doc, that includes [how to use conditions and variants](api/README.md).\n\n## Plugins\n\nOne can write an extension for _validation_ pipeline for Yatagan to implement one's custom graph inspections.\nNo additional code generation is currently supported for plugins, and they can not modify graphs under inspection.\nThis works, as for Dagger, via SPI. Read more [here](validation/spi/README.md).\n\n## Options\n\nYatagan has some options, that tweak its behavior. They are provided as normal annotation processor options.\nHowever, reflection backend requires a different approach in specifying them,\nas documented [here](rt/README.md#reflection-specific-api).\n\n| Option key                       | Default value | Description                                                                   |\n|----------------------------------|---------------|-------------------------------------------------------------------------------|\n| `yatagan.enableStrictMode`       | true          | if enabled, every _mandatory warning_ is reported as an error                 |\n| `yatagan.maxIssueEncounterPaths` | 5             | the max number of places `Encountered in` in an error message to be mentioned |\n| `yatagan.usePlainOutput`         | false         | if enabled, reporting is done in plain text, without ANSI coloring            |\n\n[D2]: https://dagger.dev/\n[KSP]: https://kotlinlang.org/docs/ksp-quickstart.html\n[DR]: https://github.com/JakeWharton/dagger-reflect\n[REF]: #yatagan-vs-dagger-api-reference\n[MEDIUM]: https://medium.com/p/eb58ca20d52f/","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyandex%2Fyatagan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyandex%2Fyatagan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyandex%2Fyatagan/lists"}