{"id":13610725,"url":"https://github.com/mars885/hilt-binder","last_synced_at":"2025-04-09T16:09:08.796Z","repository":{"id":40046341,"uuid":"332237059","full_name":"mars885/hilt-binder","owner":"mars885","description":"An annotating processing library that automatically generates Dagger Hilt's `@Binds` methods.","archived":false,"fork":false,"pushed_at":"2025-02-20T20:06:48.000Z","size":1136,"stargazers_count":71,"open_issues_count":1,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T16:09:02.572Z","etag":null,"topics":["android","annotation-processing","annotation-processor","dagger","dagger-hilt","dagger2","kotlin","kotlin-library"],"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/mars885.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"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":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-23T15:03:39.000Z","updated_at":"2025-03-03T19:22:09.000Z","dependencies_parsed_at":"2024-08-01T19:44:24.186Z","dependency_job_id":"af1fcfab-a308-4b90-8da9-abf68c7e4c94","html_url":"https://github.com/mars885/hilt-binder","commit_stats":{"total_commits":97,"total_committers":2,"mean_commits":48.5,"dds":"0.020618556701030966","last_synced_commit":"5070ab91501e04eb6f77b0aefd357e2b6d96b8d2"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mars885%2Fhilt-binder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mars885%2Fhilt-binder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mars885%2Fhilt-binder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mars885%2Fhilt-binder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mars885","download_url":"https://codeload.github.com/mars885/hilt-binder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248065283,"owners_count":21041871,"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","annotation-processing","annotation-processor","dagger","dagger-hilt","dagger2","kotlin","kotlin-library"],"created_at":"2024-08-01T19:01:47.402Z","updated_at":"2025-04-09T16:09:08.775Z","avatar_url":"https://github.com/mars885.png","language":"Kotlin","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"readme":"# HiltBinder\nAn annotating processing library that automatically generates Dagger Hilt's `@Binds` methods.\n\n[![Platform](https://img.shields.io/badge/platform-Android-green.svg)](http://developer.android.com/index.html)\n[![Download](https://img.shields.io/maven-central/v/com.paulrybitskyi/hilt-binder.svg?label=Download)](https://search.maven.org/search?q=com.paulrybitskyi.hilt-binder)\n[![Build](https://github.com/mars885/hilt-binder/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/mars885/hilt-binder/actions)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\n## Contents\n* [Motivation](#motivation)\n* [Installation](#installation)\n  * [Java](#java)\n  * [KAPT](#kapt)\n  * [KSP](#ksp)\n* [Usage](#usage)\n  * [Basics](#basics)\n  * [Hilt Components](#hilt-components)\n    * [Predefined](#predefined)\n    * [Custom](#custom)\n  * [Multibindings](#multibindings)\n  * [Qualifiers](#qualifiers)\n  * [Generic Types](#generic-types)\n* [Sample](#sample)\n* [License](#license)\n\n## Motivation\n\nThe main motivation behind this library is to eliminate boilerplate code when binding types in the Dagger Hilt library. Occasionally we have a type that should be exposed only by its supertype (a superclass or an implemented interface). In order to bind the type to its supertype in the Dagger Hilt, we have to write something like this:\n\n````kotlin\n@Module\n@InstallIn(SingletonComponent::class)\ninterface BindingsModule {\n\n  @Binds\n  fun bindType(type: Type): Supertype\n\n}\n````\n\nCan't we just automate this process and instruct the machine to generate a binding for us? This library is the answer to the given question.\n\n\n## Installation\n\nFirst of all, make sure that you've added the `mavenCentral()` repository to your top-level `build.gradle` file.\n\n````kotlin\nbuildscript {\n    //...\n    repositories {\n        //...\n        mavenCentral()\n    }\n    //...\n}\n````\n\n### Java\n\nIf you are using pure Java (no Kotlin code), add the following to your module-level `build.gradle` file.\n\n````kotlin\ndependencies {\n    implementation(\"com.paulrybitskyi:hilt-binder:1.1.3\")\n    annotationProcessor(\"com.paulrybitskyi:hilt-binder-compiler:1.1.3\")\n}\n````\n\n### KAPT\n\nIf you are using Kotlin with Java, then apply the [kapt plugin](https://kotlinlang.org/docs/reference/kapt.html) and declare the compiler dependency using `kapt` instead of `annotationProcessor`.\n\n````kotlin\nplugins {\n    kotlin(\"kapt\")\n}\n\ndependencies {\n    implementation(\"com.paulrybitskyi:hilt-binder:1.1.3\")\n    kapt(\"com.paulrybitskyi:hilt-binder-compiler:1.1.3\")\n}\n````\n\n### KSP\n\nA [KSP](https://github.com/google/ksp) implementation of the library. KSP is a replacement for KAPT to run annotation processors natively on the Kotlin compiler, significantly reducing build times.\n\nTo use the KSP implementation, go to your project's `settings.gradle.kts` file and add `google()` to `repositories` for the KSP plugin.\n\n````kotlin\npluginManagement {\n    repositories {\n       gradlePluginPortal()\n       google()\n    }\n}\n````\n\nThen, in the module's `build.gradle.kts` file, apply the KSP Gradle plguin and replace the `kapt` configuration in your build file with `ksp`.\n\n````kotlin\nplugins {\n    id(\"com.google.devtools.ksp\") version \"\u003clatestKspVersion\u003e\"\n}\n\ndependencies {\n    implementation(\"com.paulrybitskyi:hilt-binder:1.1.3\")\n    ksp(\"com.paulrybitskyi:hilt-binder-compiler:1.1.3\")\n}\n````\n\nSee the [KSP documentation](https://github.com/google/ksp/blob/main/docs/quickstart.md) for more details.\n\n## Usage\n\n### Basics\n\nThe main annotation of the library is [@BindType](https://github.com/mars885/hilt-binder/blob/master/hilt-binder/src/main/java/com/paulrybitskyi/hiltbinder/BindType.kt). The annotation has 4 optional parameters, which are going to be explained in the following examples.\n\nLet's say we want to bind a class to its superclass/interface:\n\n````kotlin\nabstract class AbstractImageLoader\ninterface ImageLoader\n\n@BindType\nclass PicassoImageLoader @Inject constructor(): AbstractImageLoader()\n\n@BindType\nclass GlideImageLoader @Inject constructor(): ImageLoader\n````\n\nWhich generates this:\n\n````java\n@Module\n@InstallIn(SingletonComponent.class)\npublic interface HiltBinder_SingletonComponentModule {\n  @Binds\n  AbstractImageLoader bind_PicassoImageLoader(PicassoImageLoader binding);\n\n  @Binds\n  ImageLoader bind_GlideImageLoader(GlideImageLoader binding);\n}\n````\n\nWhat happens if we have a class that has a superclass and also implements some interfaces? In that case, we'll have to manually specify the type we would like to bind to using `to` parameter of the annotation. For example, to bind to an interface:\n\n````kotlin\nabstract class AbstractImageLoader\ninterface ImageLoader\n\n@BindType(to = ImageLoader::class)\nclass PicassoImageLoader @Inject constructor(): AbstractImageLoader(), ImageLoader\n````\n\nWhich generates this:\n\n````java\n@Module\n@InstallIn(SingletonComponent.class)\npublic interface HiltBinder_SingletonComponentModule {\n  @Binds\n  ImageLoader bind_PicassoImageLoader(PicassoImageLoader binding);\n}\n````\n\nThe default behavior simply tries to bind to a direct superclass or interface of the annotated type. If the processor cannot deduce the type on its own (e.g., class implements multiple interfaces, has a superclass and implements an interface), then it is going to throw an error to notify you to specify the type to bind to explicitly.\n\nIt's worth mentioning that if you need to bind to a specific type in your class hierarchy (e.g., superclass of a superclass, extended interface, etc.), then you have no other option than specifying a value for the `to` parameter.\n\n### Hilt Components\n\nDagger Hilt comes with predefined components for Android, but also supports creating custom ones. First, let's see how we can install a binding into predefined components.\n\n#### Predefined\n\nYou've probably noticed that in the previous examples all the generated files have the `@InstallIn(SingletonComponent.class)` annotation. This means that by default all bindings are installed into the Hilt's predefined `SingletonComponent`. There are two ways to change a predefined component: either use the `installIn` parameter of the annotation or specify a scope annotation of a predefined component. For example, have a look at the following code:\n\n````kotlin\ninterface ImageLoader\ninterface Logger\n\n@BindType(installIn = BindType.Component.FRAGMENT)\nclass PicassoImageLoader @Inject constructor(): ImageLoader\n\n@FragmentScoped\n@BindType\nclass AndroidLogger @Inject constructor(): Logger\n````\n\nWhich generates this:\n\n````java\n@Module\n@InstallIn(FragmentComponent.class)\npublic interface HiltBinder_FragmentComponentModule {\n  @Binds\n  ImageLoader bind_PicassoImageLoader(PicassoImageLoader binding);\n\n  @Binds\n  Logger bind_AndroidLogger(AndroidLogger binding);\n}\n````\n\nObviously, the `AndroidLogger` instance will also be **scoped** to the `FragmentComponent`, unlike the `PicassoImageLoader` instance. With the `AndroidLogger` example, the library simply leverages the fact that every scope is associated with its corresponding component, therefore, there is no need to specify it again using the `installIn` parameter, though you can.\n\n#### Custom\n\nTo install a binding into a custom component, assign `BindType.Component.CUSTOM` as the value of the `installIn` parameter and specify a class of the custom component itself through the `customComponent` parameter. It should be mentioned that, unlike with predefined components, simply specifying a scope of the custom component won't work, since it's impossible to infer a class of the custom component from its scope annotation. For example, take a look at the following code:\n\n ````kotlin\n// A custom component's scope annotation\n@Scope\n@Retention(value = AnnotationRetention.RUNTIME)\nannotation class CustomScope\n\n// Declaration of a custom component itself\n@CustomScope\n@DefineComponent(parent = SingletonComponent::class)\ninterface CustomComponent\n\ninterface ImageLoader\ninterface Logger\n\n// Binding unscoped type\n@BindType(\n  installIn = BindType.Component.CUSTOM,\n  customComponent = CustomComponent::class\n)\nclass PicassoImageLoader @Inject constructor(): ImageLoader\n\n// Binding scoped type\n@CustomScope\n@BindType(\n  installIn = BindType.Component.CUSTOM,\n  customComponent = CustomComponent::class\n)\nclass AndroidLogger @Inject constructor(): Logger\n\n// Won't work, can't infer CustomComponent from CustomScope\n// @CustomScope\n// @BindType\n// class AndroidLogger @Inject constructor(): Logger\n````\n\nWhich generates the following:\n\n````java\n@Module\n@InstallIn(CustomComponent.class)\npublic interface HiltBinder_CustomComponentModule {\n  @Binds\n  ImageLoader bind_PicassoImageLoader(PicassoImageLoader binding);\n\n  @Binds\n  Logger bind_AndroidLogger(AndroidLogger binding);\n}\n````\n\n### Multibindings\n\nThe library also supports [Dagger Multibindings](https://dagger.dev/dev-guide/multibindings.html). For example, to contribute elements to a multibound set:\n\n````kotlin\ninterface UrlOpener\n\n@BindType(contributesTo = BindType.Collection.SET)\nclass BrowserUrlOpener @Inject constructor(): UrlOpener\n\n@BindType(contributesTo = BindType.Collection.SET)\nclass CustomTabUrlOpener @Inject constructor(): UrlOpener\n\n@BindType(contributesTo = BindType.Collection.SET)\nclass NativeAppUrlOpener @Inject constructor(): UrlOpener\n````\n\nWhich generates this:\n\n````java\n@Module\n@InstallIn(SingletonComponent.class)\npublic interface HiltBinder_SingletonComponentModule {\n  @Binds\n  @IntoSet\n  UrlOpener bind_BrowserUrlOpener(BrowserUrlOpener binding);\n\n  @Binds\n  @IntoSet\n  UrlOpener bind_CustomTabUrlOpener(CustomTabUrlOpener binding);\n\n  @Binds\n  @IntoSet\n  UrlOpener bind_NativeAppUrlOpener(NativeAppUrlOpener binding);\n}\n````\n\nTo contribute to a multibound map, we also need to provide the [@Mapkey](https://dagger.dev/api/latest/dagger/MapKey.html) annotation. Dagger has some default ones, like [@ClassKey](https://dagger.dev/api/latest/dagger/multibindings/ClassKey.html) and [@StringKey](https://dagger.dev/api/latest/dagger/multibindings/StringKey.html), but, unfortunately, they cannot be used in this case, because they are only applicable to methods (meaning their `@Target` annotation is equal to `ElementType.METHOD`). Therefore, the library provides its own default ones (`@MapIntKey`, `@MapLongKey`, `@MapStringKey`, and `@MapClassKey`). For example:\n\n````kotlin\ninterface SettingHandler\n\n@BindType(contributesTo = BindType.Collection.MAP)\n@MapStringKey(\"change_username\")\nclass ChangeUsernameSettingHandler @Inject constructor(): SettingHandler\n\n@BindType(contributesTo = BindType.Collection.MAP)\n@MapStringKey(\"buy_subscription\")\nclass BuySubscriptionSettingHandler @Inject constructor(): SettingHandler\n\n@BindType(contributesTo = BindType.Collection.MAP)\n@MapStringKey(\"log_out\")\nclass LogOutSettingHandler @Inject constructor(): SettingHandler\n````\n\nWhich generates this:\n\n````java\n@Module\n@InstallIn(SingletonComponent.class)\npublic interface HiltBinder_SingletonComponentModule {\n  @Binds\n  @IntoMap\n  @MapStringKey(\"change_username\")\n  SettingHandler bind_ChangeUsernameSettingHandler(ChangeUsernameSettingHandler binding);\n\n  @Binds\n  @IntoMap\n  @MapStringKey(\"buy_subscription\")\n  SettingHandler bind_BuySubscriptionSettingHandler(BuySubscriptionSettingHandler binding);\n\n  @Binds\n  @IntoMap\n  @MapStringKey(\"log_out\")\n  SettingHandler bind_LogOutSettingHandler(LogOutSettingHandler binding);\n}\n````\n\nCustom keys are also supported. For example:\n\n````kotlin\ninterface SettingHandler\n\nenum class SettingType {\n\n  CHANGE_USERNAME,\n  BUY_SUBSCRIPTION,\n  LOG_OUT\n\n}\n\n@MapKey\n@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)\n@Retention(AnnotationRetention.BINARY)\nannotation class SettingMapKey(val type: SettingType)\n\n@BindType(contributesTo = BindType.Collection.MAP)\n@SettingMapKey(SettingType.CHANGE_USERNAME)\nclass ChangeUsernameSettingHandler @Inject constructor(): SettingHandler\n\n@BindType(contributesTo = BindType.Collection.MAP)\n@SettingMapKey(SettingType.BUY_SUBSCRIPTION)\nclass BuySubscriptionSettingHandler @Inject constructor(): SettingHandler\n\n@BindType(contributesTo = BindType.Collection.MAP)\n@SettingMapKey(SettingType.LOG_OUT)\nclass LogOutSettingHandler @Inject constructor(): SettingHandler\n````\n\nWhich generates this:\n\n````java\n@Module\n@InstallIn(SingletonComponent.class)\npublic interface HiltBinder_SingletonComponentModule {\n  @Binds\n  @IntoMap\n  @SettingMapKey(SettingType.CHANGE_USERNAME)\n  SettingHandler bind_ChangeUsernameSettingHandler(ChangeUsernameSettingHandler binding);\n\n  @Binds\n  @IntoMap\n  @SettingMapKey(SettingType.BUY_SUBSCRIPTION)\n  SettingHandler bind_BuySubscriptionSettingHandler(BuySubscriptionSettingHandler binding);\n\n  @Binds\n  @IntoMap\n  @SettingMapKey(SettingType.LOG_OUT)\n  SettingHandler bind_LogOutSettingHandler(LogOutSettingHandler binding);\n}\n````\n\n### Qualifiers\n\nQualifiers are supported by the library as well. For the qualifier to be associated with the return type, you need to provide `true` to the `withQualifier` parameter of the annotation. The default value is `false`. Let's have a look at the following example:\n\n````kotlin\ninterface BooksDataStore\n\n@BindType(withQualifier = true)\n@Named(\"books_local\")\nclass BooksLocalDataStore @Inject constructor(): BooksDataStore\n\n@BindType(withQualifier = true)\n@Named(\"books_remote\")\nclass BooksRemoteDataStore @Inject constructor(): BooksDataStore\n````\n\nWhich generates this:\n\n````java\n@Module\n@InstallIn(SingletonComponent.class)\npublic interface HiltBinder_SingletonComponentModule {\n  @Binds\n  @Named(\"books_local\")\n  BooksDataStore bind_BooksLocalDataStore(BooksLocalDataStore binding);\n\n  @Binds\n  @Named(\"books_remote\")\n  BooksDataStore bind_BooksRemoteDataStore(BooksRemoteDataStore binding);\n}\n````\n\n### Generic Types\n\nBinding generic types is identical to binding regular types. For example:\n\n````kotlin\ninterface StreamingService\u003cT\u003e\n\nclass Netflix\nclass Hulu\n\n@BindType\nclass NetflixService @Inject constructor(): StreamingService\u003cNetflix\u003e\n\n@BindType(to = StreamingService::class)\nclass HuluService @Inject constructor(): StreamingService\u003cHulu\u003e\n````\n\nGenerates the following:\n\n````java\n@Module\n@InstallIn(SingletonComponent.class)\npublic interface HiltBinder_SingletonComponentModule {\n  @Binds\n  StreamingService\u003cNetflix\u003e bind_NetflixService(NetflixService binding);\n\n  @Binds\n  StreamingService\u003cHulu\u003e bind_HuluService(HuluService binding);\n}\n````\n\nHowever, if a binding is contributed to either collection (`SET` or `MAP`), then any type parameters of a generic return type will be replaced with wildcards. For example:\n\n````kotlin\ninterface StreamingService\u003cT\u003e\n\nclass Netflix\nclass Hulu\n\n@BindType(contributesTo = BindType.Collection.SET)\nclass NetflixService @Inject constructor(): StreamingService\u003cNetflix\u003e\n\n@BindType(\n  to = StreamingService::class,\n  contributesTo = BindType.Collection.SET\n)\nclass HuluService @Inject constructor(): StreamingService\u003cHulu\u003e\n````\n\nGenerates the following:\n\n````java\n@Module\n@InstallIn(SingletonComponent.class)\npublic interface HiltBinder_SingletonComponentModule {\n  @Binds\n  @IntoSet\n  StreamingService\u003c?\u003e bind_NetflixService(NetflixService binding);\n\n  @Binds\n  @IntoSet\n  StreamingService\u003c?\u003e bind_HuluService(HuluService binding);\n}\n````\n\n## Sample\n\nThe project contains a [sample](https://github.com/mars885/hilt-binder/tree/master/sample/src/main/java/com/paulrybitskyi/hiltbinder/sample) application that illustrates the aforementioned examples as well as some advanced ones.\n\nApart from the sample, you can also take a look at the following repositories that heavily utilize this library:\n- [Gamedge](https://github.com/mars885/gamedge) - An Android application for browsing video games and checking the latest gaming news from around the world.\n- [DocSkanner](https://github.com/mars885/doc-skanner) - An Android application that makes it possible to automatically scan and digitize documents from photos.\n\n## License\n\nHiltBinder is licensed under the [Apache 2.0 License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmars885%2Fhilt-binder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmars885%2Fhilt-binder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmars885%2Fhilt-binder/lists"}