Dagger-Hilt Module and Binds Annotations #3

ibrahimcanerdogan
7 min readMay 20, 2023

--

Photo by Jack Hunter on Unsplash

In Android development, Hilt is a dependency injection (DI) framework provided by Google. Hilt is built on top of the popular DI library called Dagger, and it simplifies the process of setting up and managing dependencies in your Android application.

Hilt provides two types of annotations: @Module and @Binds.

Module Annotation

  1. The @Module annotation is used to mark a class as a Dagger module in Hilt. A module is responsible for providing instances of dependencies that can be injected into other classes. Here's why @Module is used:
  • Dependency Provision: The main purpose of a module is to define methods that provide instances of dependencies or define how to obtain them. By annotating a class with @Module, you signal to Hilt that the class contains methods that contribute to the dependency graph.
  • Scoping: Modules can be used to define the scope of dependencies. By associating a module with a specific component using @InstallIn, you can ensure that the dependencies provided by the module are scoped to the component. This allows for more fine-grained control over the lifecycle and availability of dependencies.
  • Organization and Modularity: Modules help in organizing and modularizing your dependency injection setup. By separating the dependency creation logic into modules, you can improve code maintainability, reusability, and testability. Each module can focus on a specific set of dependencies, making it easier to reason about and update the dependency graph.

@InstallIn Annotation: The @InstallIn annotation is used to specify the component where a module should be installed in Hilt. Here's why @InstallIn is used:

  • Component Association: Hilt organizes dependencies into components, and @InstallIn is used to associate a module with a specific component. By using @InstallIn, you define the component where the module's provided dependencies will be available for injection. It establishes a relationship between the module and the component, allowing Hilt to handle the creation and management of dependencies within the specified component.
  • Scoping and Lifecycle: The @InstallIn annotation ensures that the dependencies provided by the module are scoped correctly within the associated component's lifecycle. Hilt supports various components like ApplicationComponent, ActivityComponent, FragmentComponent, etc. By installing a module in a specific component, you control the visibility and availability of the dependencies within that component's lifecycle.
  • Simplified Configuration: @InstallIn simplifies the configuration of dependency injection in Hilt. By annotating a module with @InstallIn and specifying the component, you no longer need to manually configure the bindings and associations between modules and components. Hilt takes care of generating the necessary Dagger code based on the annotations, reducing the boilerplate code required for manual configuration.

Overall, the combination of @Module and @InstallIn annotations in Hilt allows you to define and organize the creation and provisioning of dependencies within your application. Modules provide a way to declare how to obtain dependencies, while @InstallIn associates the module with a specific component, defining the scope and availability of the dependencies within that component's lifecycle. This combination simplifies the configuration, enhances code organization, and improves modularity and maintainability in your dependency injection setup.

This annotation is used to mark a class as a Dagger module. A module is responsible for providing instances of dependencies that can be injected into other classes. You can define methods inside the module class and annotate them with @Provides to specify how to create or obtain instances of specific dependencies.

@Module
class AppModule {

@Provides
fun provideSomeDependency(): SomeDependency {
return SomeDependencyImpl()
}

// Other methods providing dependencies...
}

Binds Annotation

The @Binds annotation in Hilt is used to declare that a method in a module binds a specific implementation to its corresponding interface or abstract class.

The primary purpose of @Binds is to simplify the code and make it more concise when you want to provide an implementation for an interface or an abstract class. Instead of writing a method with @Provides in the module to create an instance of the implementation, you can use @Binds to directly specify the binding relationship between the interface and the implementation.

Here are a few reasons why you might choose to use @Binds annotation:

  1. Code simplicity and readability: The @Binds annotation allows you to directly express the relationship between an interface and its implementation without the need for additional code. It makes the code more concise and easier to understand by eliminating the need to write a separate provider method using @Provides in the module.
  2. Compile-time safety: When you use @Binds, the Hilt compiler performs static analysis on the code and verifies that the binding is correct at compile-time. This means that if there's a problem with the binding, such as providing an incompatible implementation, it will be caught during compilation rather than at runtime.
  3. Performance optimization: By using @Binds, you can inform Dagger/Hilt that the implementation being bound is the only possible implementation for the given interface. This knowledge allows Dagger/Hilt to perform optimizations such as tree shaking, where it removes unused code during the build process. This can result in smaller APK sizes and improved performance.
  4. Support for interfaces and abstract classes: In object-oriented programming, it’s common to define interfaces or abstract classes to define contracts that can be implemented by multiple classes. The @Binds annotation provides a convenient way to specify the relationship between the interface or abstract class and its implementation without the need for providing explicit creation logic.

It’s important to note that @Binds can only be used in abstract classes or interfaces inside a Dagger module, as it's responsible for binding the implementation to the abstract type. The implementation itself cannot be instantiated directly and must be provided somewhere else in the codebase.

Overall, the @Binds annotation in Hilt simplifies the process of binding an implementation to an interface or abstract class, resulting in cleaner and more readable code, compile-time safety, and potential performance optimizations.

@Module
interface SomeModule {

@Binds
fun bindSomeDependency(someDependency: SomeDependencyImpl): SomeDependency

// Other method bindings...
}

In this example, bindSomeDependency method binds SomeDependencyImpl class to SomeDependency interface, allowing it to be used throughout the application when SomeDependency is requested.

Both @Module and @Binds annotations are used in conjunction with Hilt's component annotations, such as @Component or @AndroidEntryPoint, to enable dependency injection in your Android application.

Example-1

First, let’s define an interface and its implementation:

interface SomeDependency {
fun doSomething()
}

class SomeDependencyImpl @Inject constructor() : SomeDependency {
override fun doSomething() {
// Implementation logic here
println("Doing something...")
}
}

Next, we’ll create a module using @Module annotation and provide an instance of SomeDependency using @Provides:

@Module
@InstallIn(SingletonComponent::class) // Specifies the component scope
object AppModule {

@Provides
fun provideSomeDependency(): SomeDependency {
return SomeDependencyImpl()
}
}

In this example, we use @InstallIn annotation to specify the component scope, which is SingletonComponent. This means that the instances provided by this module will have a singleton scope.

Now, let’s use @Binds annotation in a separate module to bind the interface SomeDependency to its implementation SomeDependencyImpl:

@Module
@InstallIn(ActivityComponent::class) // Specifies the component scope
interface SomeModule {

@Binds
fun bindSomeDependency(someDependency: SomeDependencyImpl): SomeDependency
}

In this module, we use @InstallIn annotation to specify the component scope as ActivityComponent. This means that the instances provided by this module will have an activity scope.

Finally, in your activity or fragment, you can use Hilt’s @Inject annotation to inject the SomeDependency instance:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

@Inject
lateinit var someDependency: SomeDependency

// ...
}

With these annotations and modules set up, Hilt will automatically generate the necessary code to provide and inject the dependencies in your Android application. The SomeDependency instance will be injected into the MainActivity by Hilt, and you can use it as needed.

Example-2

First, let’s define an interface Logger:

interface Logger {
fun log(message: String)
}

Next, we’ll create a concrete implementation of the Logger interface:

class ConsoleLogger : Logger {
override fun log(message: String) {
println("Logging: $message")
}
}

Now, let’s create a module called LoggerModule using the @Module annotation. Inside the module, we'll define a method that provides an instance of the Logger interface by binding it to the ConsoleLogger implementation using the @Binds annotation:

@Module
abstract class LoggerModule {

@Binds
abstract fun bindLogger(consoleLogger: ConsoleLogger): Logger
}

The bindLogger method binds the ConsoleLogger class to the Logger interface, allowing us to use Logger throughout the application.

Next, let’s create a class called MainActivity that requires an instance of the Logger interface:

class MainActivity {

@Inject
lateinit var logger: Logger

fun doSomething() {
logger.log("Hello, Ibrahim Can Erdogan!")
}
}

In the MainActivity class, we annotate the logger property with @Inject to indicate that we want Hilt to inject an instance of the Logger interface into it.

To enable Hilt and make the dependencies available, we need to create a Hilt component. Let’s create an ApplicationComponent that uses the LoggerModule:

@Component(modules = [LoggerModule::class])
interface ApplicationComponent {
fun inject(mainActivity: MainActivity)
}

The @Component annotation marks the interface as a Hilt component, and we specify the modules to be used within the component using the modules parameter.

Example-3

@Module
interface StorageModule {

@Binds
fun bindStorageManager(storageManager: StorageManagerImpl): StorageManager
}

n this example, the StorageModule interface is annotated with @Module, indicating that it's a Dagger module. The bindStorageManager method is annotated with @Binds, indicating that it binds the StorageManagerImpl class to the StorageManager interface. This allows instances of StorageManager to be injected throughout the application and resolved to StorageManagerImpl.

Note that in the second example, we use an interface instead of a class for the module. It’s valid to define modules as interfaces in Hilt.

These examples demonstrate how to use the @Module and @Binds annotations to define Dagger modules and provide or bind instances of dependencies. Remember that you'll also need to set up the Hilt components, such as @Component or @AndroidEntryPoint, in your Android application to enable dependency injection using Hilt.

I hope it was useful. You can follow me on my social media accounts.

LINKEDIN

UDEMY

GITHUB

--

--

ibrahimcanerdogan
ibrahimcanerdogan

Written by ibrahimcanerdogan

Hi, My name is Ibrahim, I am developing ebebek android app within Ebebek. I publish various articles in the field of programming and self-improvement.

No responses yet