{"id":13608046,"url":"https://github.com/afollestad/ulfberht","last_synced_at":"2026-01-14T03:26:31.878Z","repository":{"id":57716573,"uuid":"197049570","full_name":"afollestad/ulfberht","owner":"afollestad","description":"🗡️ A small but powerful \u0026 opinionated DI library. Written in Kotlin, and powered by annotation processing.","archived":true,"fork":false,"pushed_at":"2023-01-05T22:10:34.000Z","size":688,"stargazers_count":251,"open_issues_count":6,"forks_count":8,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-04-12T14:38:26.223Z","etag":null,"topics":["annotation-processor","dependency-injection","di","kotlin","lifecycle","viewmodel"],"latest_commit_sha":null,"homepage":"https://af.codes","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/afollestad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"afollestad","ko_fi":"afollestad"}},"created_at":"2019-07-15T18:13:00.000Z","updated_at":"2024-04-20T12:26:02.000Z","dependencies_parsed_at":"2023-02-04T19:46:41.512Z","dependency_job_id":null,"html_url":"https://github.com/afollestad/ulfberht","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/afollestad/ulfberht","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fulfberht","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fulfberht/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fulfberht/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fulfberht/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/afollestad","download_url":"https://codeload.github.com/afollestad/ulfberht/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fulfberht/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408843,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["annotation-processor","dependency-injection","di","kotlin","lifecycle","viewmodel"],"created_at":"2024-08-01T19:01:23.816Z","updated_at":"2026-01-14T03:26:31.862Z","avatar_url":"https://github.com/afollestad.png","language":"Kotlin","funding_links":["https://github.com/sponsors/afollestad","https://ko-fi.com/afollestad"],"categories":["IoC","Kotlin"],"sub_categories":[],"readme":"Note: Dagger2 and [Anvil](https://github.com/square/anvil) should be used in production apps.\nThis is now mostly just an experiment with kapt and Kotlinpoet,\nIR should be used rather than Kapt these days. \n\n# Ulfberht\n\n[ ![Maven Central](https://img.shields.io/maven-central/v/com.afollestad/ulfberht?style=flat\u0026label=Maven+Central) ](https://repo1.maven.org/maven2/com/afollestad/ulfberht)\n[![Android CI](https://github.com/afollestad/ulfberht/workflows/Android%20CI/badge.svg)](https://github.com/afollestad/ulfberht/actions?query=workflow%3A%22Android+CI%22)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\n\u003e The Vikings were among the fiercest warriors of all time. Yet only a select few carried the \n\u003e ultimate weapon of their era: the feared Ulfberht sword. Fashioned using a process that would \n\u003e remain unknown to the Vikings' rivals for centuries, the Ulfberht was a revolutionary high-tech \n\u003e tool as well as a work of art.\n\n*A little more bad-ass than a Dagger.* Dependency injection is a technique in which an application \nsupplies dependencies of an object. \"Dependencies\" in this context are not dependencies like in a \nGradle file. Dependencies are services (i.e. APIs, classes) that are needed in certain parts of \nyour code.\n\n---\n\n# Table of Contents\n\n1. [Why Choose Ulfberht?](#why-choose-ulfberht)\n2. [Gradle Dependency](#gradle-dependency)\n3. [Modules](#modules)\n    1. [Binding](#binding)\n    2. [Providing](#providing)\n    3. [Singletons](#singletons)\n    4. [Qualifiers](#qualifiers)\n4. [Components](#components)\n    1. [Basics](#basics)\n    2. [Child Components](#child-components)\n    4. [Scoping](#scoping)\n5. [Putting Modules and Components to Use - Injection](#putting-modules-and-components-to-use---injection)\n6. [Runtime Dependencies](#runtime-dependencies)\n7. [Android](#android)\n    1. [ScopeOwners](#scope-owners)\n    2. [ViewModels](#viewmodels)\n\n---\n \n# Why Choose Ulfberht?\n\nI wrote Ulfberht as an experiment to see if I could make [Dagger](https://github.com/google/dagger) \nstyle dependency injection a bit easier, and quicker to pickup for newbies. I wanted something \nlightweight, with less Boilerplate code and more automation. I wanted something annotation-processor \nbased rather than reflection-based, while still being written in Kotlin. And I wanted better built-in \nscoping support, especially on Android. _This is the result._\n\nYou may be wondering what makes this library different than KOIN or other Kotlin \"DI\" libraries? \nLibraries like KOIN are just service locators - you need to build the dependency graph manually, \nfilling in constructors, etc. Annotation processor based DI libraries handle this for you with \ncode generation, so you *don't* need to write boilerplate and you *don't* need to use reflection.\n\n---\n \n# Gradle Dependency\n\n```gradle\ndependencies {\n  implementation \"com.afollestad:ulfberht:0.6.0\"\n  kapt \"com.afollestad:ulfbert-processor:0.6.0\"\n}\n```\n\n---\n\n# Modules\n\nA module is a class that gives instructions for dependency instantiation.\n\n### Binding\n\nOne way a module can instruct object instantiation is through binding. If you've used Dagger, this \nshould be straightforward. \n\nIn this example below, we bind an interface with a concrete implementation. Whenever you inject \n`Demo`, you inject the `DemoImpl` implementation of it.\n\nTaking this interface and implementation...\n\n```kotlin\ninterface Demo {\n  fun myMethod()\n}\n\nclass DemoImpl : Demo {\n  override fun myMethod() {\n    ...\n  }\n}\n```\n\n...they can be bound in a `@Module` interface:\n\n```kotlin\n@Module\ninterface DemoModule {\n  @Binds \n  fun demoClass(impl: DemoImpl): Demo\n}\n```\n\n---\n\nIf `DemoImpl` itself had dependencies in its constructor, those must be bound or provided as well \nso that they can be injected too...\n\n```kotlin\ninterface SomethingElse\nclass SomethingElseImpl : SomethingElse\n\ninterface Demo {\n  fun myMethod()\n}\nclass DemoImpl(val somethingElse: SomethingElse) : Demo {\n  override fun myMethod() {\n    ...\n  }\n}\n```\n\n...with a module setup like this:\n\n```kotlin\n@Module\ninterface DemoModule {\n  @Binds\n  fun demoClass(impl: DemoImpl): Demo\n  \n  @Binds\n  fun somethingElse(impl: SomethingElseImpl): SomethingElse\n}\n```\n\n### Providing\n\nAnother way a module can instruct object instantiation is through providing. This should also be a \nfamiliar concept for Dagger users. \n\nProviding is more flexible than binding. You tell the library how instantiation should happen, and \nwhat you provide _does not_ need all of its constructor parameters to be injectable. Notice that \na module which can use `@Provides` must be a `abstract class` rather than an `interface`.\n\n```kotlin\nclass SomethingElse\nclass Demo(val somethingElse: SomethingElse)\n\n@Module\nabstract class DemoModule {\n  @Provides \n  fun demoClass(): Demo {\n    val somethingElse = SomethingElse()\n    return Demo(somethingElse)\n  }\n}\n```\n\n`@Provides` methods can have parameters, which the library will fill from the dependency graph \nas well. This could be a dependency provided in another module or even another component's module.\n\n```kotlin\nclass SomethingElse\nclass Demo(val somethingElse: SomethingElse)\n \n@Module\ninterface DemoModule {\n  @Provides \n  fun demoClass(somethingElse: SomethingElse): Demo {\n    return Demo(somethingElse)\n  }\n  \n  @Provides \n  fun somethingElse(): SomethingElse {\n    return SomethingElse()\n  }\n}\n```\n\n### Singletons\n\nThere's a `@Singleton` annotation that can be used to mark `@Binds` and `@Provides` methods. When \nit's used, a module in a specific component will hold the same instance of the provided object until \nthe module is destroyed. *If you were to use the same module in two different components, \n`ComponentA` and `ComponentB`, each component would have a separate singleton instance of what \nyou're providing. They wouldn't share between each other.*\n\n```kotlin\n@Module\ninterface DemoModule1 {\n  @Binds @Singleton \n  fun demoClass1(impl: Demo1Impl): Demo1\n}\n\n@Module\nabstract class DemoModule2 {\n  @Provides @Singleton \n  fun demoClass2(): Demo2 {\n    return Demo2Impl()\n  }\n}\n```\n\nEvery time injection pulls `Demo1` and `Demo2`, it will be the same cached instances.\n\n---\n\n# Components\n\nA component is a class that takes a set of [modules](#modules), and knows how to inject what they \ncollectively bind/provide into a target object.\n\n### Basics\n\nA basic component looks like this:\n\n```kotlin\n@Component(modules = [DemoModule::class])\ninterface DemoComponent {\n  fun inject(target: SomeClass)\n}\n```\n\nYou could include multiple items in the array of the `@Component`'s `modules` parameter. \nYou can also define a void (no return type) `inject` method for every class that the component \ncan inject into.\n\n### Child Components\n\nIn a real application, you'd probably have a hierarchy of components. Components operate at a \ncertain level - for an example you could have a component that's alive for the entire lifetime of \nthe application, while you would have more short-lived components that are alive when specific \nscreens of the application are. *You build this hierarchy by assigning child components.*\n\n\u003cimg src=\"https://github.com/afollestad/ulfberht/blob/master/art/plain_diagram.png?raw=true\" /\u003e\n\nWhen you inject something at the bottom of the chain, you're able to inject things that are \nbound/provided throughout the chain all the way up to the top.\n\n---\n\nA component hierarchy is built by assigning *children* to components. The code below mimics the \ndiagram above.\n\n```kotlin\n@Component(\n  children = [Component2::class, Component3::class],\n  modules = [Module1::class, Module2::class]\n)\ninterface Component1\n\n@Component(\n  children = [Component4::class, Component5::class],\n  modules = [Module3::class, Module4::class]\n)\ninterface Component2\n\n@Component(\n  children = [Component6::class, Component7::class],\n  modules = [Module5::class, Module6::class]\n)\ninterface Component3\n\n@Component(modules = [Module7::class, Module8::class])\ninterface Component4\n\n@Component(modules = [Module9::class, Module10::class])\ninterface Component5\n\n@Component(modules = [Module11::class, Module12::class])\ninterface Component6\n\n@Component(modules = [Module13::class, Module14::class])\ninterface Component7\n```\n\n### Scoping\n\nIn many applications, especially mobile applications, you generally do not want to keep things around \nfor the entire lifecycle of the app. \n\nSay you're on a login screen, and need to inject an authentication dependency -- you probably only \nneed access to that object on the login screen. Once you leave, that authenticator should go away. \nScoping allows you to achieve this easily.\n\n---\n\n`Scope`'s are associated with components. When a scope is exited, components in that scope destroy \nthemselves along with their modules, and anything that the modules may be storing. To scope a component, \nthere's a simple parameter to add on the `@Component` annotation.\n\n```kotlin\n// Using constants is encouraged so you can share values throughout your app.\nconst val FIRST_SCOPE = \"first scope\"\n\n@Component(\n  scope = FIRST_SCOPE, \n  modules = [MyModule::class]\n)\ninterface MyComponent\n```\n\nYou can then retrieve an instance of this scope. There are a few things you can do with it:\n\n```kotlin\nval scope: Scope = getScope(FIRST_SCOPE)\n\n// Hook into its lifecycle, which currently is just an exit event.\nscope.addObserver(object : ScopeObserver {\n  override fun onExit() {\n    ...\n  }\n})\n\n// You can tell the scope to exit, destroying its children.\nscope.exit()\n```\n\nWhen you use the `component\u003c\u003e()` method to retrieve a component with a scope, that component is \nattached to its scope. There can be multiple components in a scope. \n\nWhen you call `exit()` on that scope, every component attached to it is destroyed. All modules in \nthose components go with them, along with any stored singletons amd child components. The next time \nyou call `component\u003c\u003e()` for a destroyed component, a new instance is created.\n\n---\n\nIf you use parenting _and_ scoping together:\n\n```kotlin\nconst val PARENT_SCOPE = \"i'm a parent\"\n\n@Component(\n  scope = PARENT_SCOPE, \n  children = [ChildComponent::class],\n  modules = [Module1::class]\n)\ninterface ParentComponent\n\n@Component(modules = [Module2::class])\ninterface ChildComponent\n\n// This would destroy both components\ngetScope(PARENT_SCOPE).exit()\n```\n\n*Parent components will destroy all of their children (components and their modules) as well.*\n\n\u003cimg src=\"https://github.com/afollestad/ulfberht/blob/master/art/destruction_diagram.png?raw=true\" /\u003e \n\n---\n\n# Putting Modules and Components to Use - Injection\n\nTo perform injection, you need to retrieve the component that's able to inject into your target.\n\n```kotlin\n@Component(...)\ninterface Component5 {\n  fun inject(activity: MyActivity)\n}\n\nclass MyActivity : AppCompatActivity() {\n  @Inject lateinit var someDependency: NeededClass\n  \n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    \n    // Perform injection, `SomeComponent` would have an inject() method defined for `MyActivity`\n    component\u003cComponent5\u003e().inject(this)\n    \n    // Use the injected dependency!\n    someDependency.helloWorld()\n  }\n}\n```\n\nThis code assumes that one of the modules going up the graph from `Component5` can supply \n`NeededClass` with a `@Binds` or `@Provides` method.\n\n\u003cimg src=\"https://github.com/afollestad/ulfberht/blob/master/art/inject_diagram.png?raw=true\" /\u003e\n\n---\n\n# Qualifiers\n\nQualifiers are simple identifiers that associate a type with a very specific bound or provided \ninstance. A qualifier is a special type of annotation, which is defined like this:\n\n```kotlin\n@Qualifier\nannotation class DemoQualifier1\n\n@Qualifier\nannotation class DemoQualifier2\n```\n\nYou use it to mark `@Binds` and `@Provides` functions:\n\n```kotlin\n@Module\ninterface DemoModule1 {\n  @Binds @Singleton @DemoQualifier1\n  fun demoClass(impl: Demo1Impl): Demo1\n}\n\n@Module\nabstract class DemoModule2 {\n  @Provides @Singleton @DemoQualifier2\n  fun demoClass(): Demo1 {\n    return Demo1Impl()\n  }\n}\n```\n\nThen, you can mark constructor parameters with it...\n\n```kotlin\nclass SomeInjectedClass(\n  @DemoQualifier1 val someDependency: Demo1,\n  @DemoQualifier2 val anotherDependency: Demo1\n) {\n  ...\n}\n```\n\n...along with `@Inject` targets (the `field:` prefix on the annotation name is important in Kotlin):\n\n```kotlin\nclass SomeClass {\n  @Inject @field:DemoQualifier1\n  lateinit var someDependency1: Demo1\n  @Inject @field:DemoQualifier2 \n  lateinit var someDependency2: Demo1\n\n  init {\n    component\u003cSomeComponent\u003e().inject(this)\n  }\n}\n```\n\nYou will get two completely separate instances of `Demo1`, since two different qualifiers are being \nused. `@Singleton` was applied for demo purposes to show that it'll store two different instances.\nBut even without that, you're providing two different things. This could be useful if you were \nproviding primitives, like strings, or an interface for something like preferences. There's a lot of\npossibilities. \n\n---\n\n# Runtime Dependencies\n\nSometimes your app may need to be able to inject something that is defined at runtime, something \nthat cannot be constructed in a module. A good example of when this would be necessary is in \nan Android application, like if you needed to inject the Application context.\n\nFirst, you tag constructor parameters or fields that need to be provided at runtime with a \nqualifier annotation, which is discussed in [Qualifiers](#qualifiers) above.\n\n```kotlin\n@Qualifier\nannotation class AppContext\n\n@Qualifier\nannotation class ApiKey\n\nclass StringRetriever(\n  @AppContext val appContext: Context,\n  @ApiKey val apiKey: String\n) {\n  fun getString(@IdRes res: Int): String {\n    return appContext.resources.getString(res)\n  }\n}\n```\n\nAt injection time, you pass mapped runtime dependencies into the `component\u003c\u003e()` method. They are \navailable for injection until the component is destroyed, or its parents destroy it. \n\n```kotlin\nclass LoginActivity : AppCompatActivity() {\n  @Inject \n  lateinit var stringRetriever: StringRetriever  \n  \n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    \n    component\u003cLoginComponent\u003e(\n      AppContext::class to applicationContext,\n      ApiKey::class to \"hello, world!\"\n    ).inject(this)\n  }\n}\n```\n\nRuntime dependencies in a component are made available to all of the component's children too. \nIn an Android application, providing the application context at the `Application` level will make \nit available to all Activities and Fragments that use child components.\n\n---\n\n# Android\n\n### ScopeOwners\n\nOn Android, you can automatically attach scopes to `LifecycleOwner`'s, such as:\n* `Fragment` (from `androidx.app`)\n* `AppCompatActivity`/`FragmentActivity`\n* `androidx.lifecycle.ViewModel`\n\n_(these all implement the `LifecycleOwner` interface)_\n\n---\n\nFirst, setup your components and modules as you would normally:\n\n```kotlin\nobject ScopeNames {\n  const val LOGIN_SCOPE = \"scope_login\"\n  const val MAIN_SCOPE = \"scope_main\"\n}\n\n@Component(\n  children = [LoginComponent::class, MainComponent::class],\n  modules = [AppModule::class]\n)\ninterface AppComponent {\n  fun inject(app: Application)\n}\n\n@Component(\n  scope = ScopeNames.LOGIN_SCOPE,\n  modules = [LoginModule::class]\n)\ninterface LoginComponent {\n  fun inject(activity: LoginActivity)\n}\n\n@Component(\n  scope = ScopeNames.MAIN_SCOPE,\n  modules = [MainModule::class]\n)\ninterface MainComponent {\n  fun inject(activity: MainActivity)\n}\n```\n\nThen, you annotate your `LifecycleOwner`'s with the `ScopeOwner` annotation:\n\n```kotlin\n@ScopeOwner(LOGIN_SCOPE)\nclass LoginActivity : AppCompatActivity() {\n  \n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    component\u003cLoginComponent\u003e().inject(this)\n  }\n}\n```\n\nSince `LoginComponent` is being injected, _and_ because it's marked as being in `LOGIN_SCOPE`, it \nwill automatically destroy itself when `LoginActivity` is destroyed. \n\n---\n\n### ViewModels\n\nOn Android, injecting AndroidX `ViewModel`'s is supported. You don't have to do anything special, \njust inject the `ViewModel` as you would inject anything else. \n\nHowever, you can only inject a `ViewModel` into an `androidx.fragment.app.Fragment` or \n`androidx.fragment.app.FragmentActivity` (includes `AppCompatActivity` and descendants). Why? \nInternally, Ulfberht's generated code delegates through `ViewModelProviders` which must attach to \nan Activity or Fragment.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafollestad%2Fulfberht","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fafollestad%2Fulfberht","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafollestad%2Fulfberht/lists"}