Dependency Injection in Android Introduction to Dependency Injection Dependency Injection (DI) is a design pattern used to implement Inversion of Control (IoC) where the control of creating and managing dependencies is transferred from the application to an external entity. This helps in creating more modular, testable, and maintainable code. It is a technique where the responsibility of creating objects is transferred to other parts of the code. This promotes loose coupling, making the code more modular and easier to manage. Classes often need references to other classes to function properly. For instance, consider a Library class that requires a Book class. These necessary classes are known as dependencies. The Library class depends on having an instance of the Book class to operate. There are three primary ways for a class to obtain the objects it needs: Self-construction: The class creates and initializes its own dependencies. For example, the Library class would create and initialize its own instance of the Book class. External retrieval: The class retrieves dependencies from an external source. Some Android APIs, such as Context getters and getSystemService(), work this way. Dependency Injection: Dependencies are provided to the class, either when it is constructed or through methods that require them. For example, the Library constructor would receive a Book instance as a parameter. The third option is dependency injection! With DI, you provide the dependencies of a class rather than having the class instance obtain them itself. Example Without Dependency Injection Without DI, a Library that creates its own Book dependency might look like this: class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } } This is not an example of DI because the Library class constructs its own Book. This can be problematic because: Tight coupling: Library and Book are tightly coupled. An instance of Library uses one type of Book, making it difficult to use subclasses or alternative implementations. Testing difficulties: The hard dependency on Book makes testing more challenging. Library uses a real instance of Book, preventing the use of test doubles to modify Book for different test cases. Example With Dependency Injection With DI, instead of each instance of Library constructing its own Book object, it receives a Book object as a parameter in its constructor: class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); } The main function uses Library. Since Library depends on Book, the app creates an instance of Book and then uses it to construct an instance of Library. The benefits of this DI-based approach are: Reusability ofLibrary: You can pass in different implementations of Book to Library. For example, you might define a new subclass of Book called EBook that you want Library to use. With DI, you simply pass an instance of EBook to Library, and it works without any further changes. Easy testing ofLibrary: You can pass in test doubles to test different scenarios. Another DI Example Consider a scenario where a NotificationService class relies on a Notification class. Without DI, the NotificationService directly creates an instance of Notification, making it difficult to use different types of notifications or to test the service with various notification implementations. To illustrate DI, let’s refactor this example: interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } } Now, NotificationService depends on the Notification interface rather than a specific class. This allows different implementations of Notification to be used interchangeably. You can set the implementation you want to use through the sendNotification method: NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification()); Methods of Dependency Injection in Android There are three main types of DI: Method (Interface) Injection: Dependencies are passed through methods that the class can access via an interface or another class. The previous example demonstrates method injection. Constructor Injection: Dependencies are passed to the class through its constructor. class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } } 3. Field Injection (or Setter Injection): Certain Android framework classes, such as activities and fragments, are instantiated by the system, so constructor injection is not possible. With field injection, dependencies are instantiated after the class is created. class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } } 4. Method Injection: Dependencies are provided through methods, often using the @Inject annotation. Advantages of Dependency Injection Classes become more reusable and less dependent on specific implementations. This is due to the inversion of control, where classes no longer manage their dependencies but work with any configuration provided. Dependencies are part of the API surface and can be verified at object creation or compile time, making refactoring easier. Since a class does not manage its dependencies, different implementations can be passed during testing to cover various scenarios. Automated Dependency Injection In the previous example, you manually created, provided, and managed the dependencies of different classes without using a library. This approach is known as manual dependency injection. While it works for simple cases, it becomes cumbersome as the number of dependencies and classes increases. Manual dependency injection has several drawbacks: Boilerplate Code: For large applications, managing all dependencies and connecting them correctly can result in a lot of repetitive code. In a multi-layered architecture, creating an object for a top layer requires providing all dependencies for the layers below it. For instance, to build a computer, you need a CPU, a motherboard, RAM, and other components; and a CPU might need transistors and capacitors. Complex Dependency Management: When you can’t construct dependencies beforehand — such as with lazy initializations or scoping objects to specific flows in your app — you need to write and maintain a custom container (or dependency graph) to manage the lifetimes of your dependencies in memory. Libraries can automate this process by creating and providing dependencies for you. These libraries fall into two categories: Reflection-based Solutions: These connect dependencies at runtime. Static Solutions: These generate code to connect dependencies at compile time. Dagger is a popular dependency injection library for Java, Kotlin, and Android, maintained by Google. Dagger simplifies DI in your app by creating and managing the dependency graph for you. It provides fully static, compile-time dependencies, addressing many of the development and performance issues associated with reflection-based solutions like Guice. Reflection-based Solutions These frameworks connect dependencies at runtime: Toothpick: A runtime DI framework that uses reflection to connect dependencies. It’s designed to be lightweight and fast, making it suitable for Android applications. Static Solutions These frameworks generate code to connect dependencies at compile time: Hilt: Built on top of Dagger, Hilt provides a standard way to incorporate Dagger dependency injection into an Android application. It simplifies the setup and usage of Dagger by providing predefined components and scopes. Koin: A lightweight and simple DI framework for Kotlin. Koin uses a DSL to define dependencies and is easy to set up and use. Kodein: A Kotlin-based DI framework that is easy to use and understand. It provides a simple and flexible API for managing dependencies. Alternatives to Dependency Injection An alternative to dependency injection is the service locator pattern. This design pattern also helps decouple classes from their concrete dependencies. You create a class known as the service locator that creates and stores dependencies, providing them on demand. object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() } The service locator pattern differs from dependency injection in how dependencies are consumed. With the service locator pattern, classes request the dependencies they need; with dependency injection, the app proactively provides the required objects. What is Dagger 2? Dagger 2 is a popular DI framework for Android. It uses compile-time code generation and is known for its high performance. Dagger 2 simplifies the process of dependency injection by generating the necessary code to handle dependencies, reducing boilerplate and improving efficiency. Dagger 2 is an annotation-based library for dependency injection in Android. Here are the key annotations and their purposes: @Module: Used to define classes that provide dependencies. For example, a module can provide an ApiClient for Retrofit. @Provides: Annotates methods in a module to specify how to create and return dependencies. @Inject: Used to request dependencies. Can be applied to fields, constructors, and methods. @Component: An interface that bridges @Module and @Inject. It contains all the modules and provides the builder for the application. @Singleton: Ensures a single instance of a dependency is created. @Binds: Used in abstract classes to provide dependencies, similar to @Provides but more concise. Dagger Components Dagger can generate a dependency graph for your project, allowing it to determine where to obtain dependencies when needed. To enable this, you need to create an interface and annotate it with @Component. Within the @Component interface, you define methods that return instances of the classes you need (e.g., BookRepository). The @Component annotation instructs Dagger to generate a container with all the dependencies required to satisfy the types it exposes. This container is known as a Dagger component, and it contains a graph of objects that Dagger knows how to provide along with their dependencies. Example Let’s consider an example involving a LibraryRepository: Annotate the Constructor: Add an @Inject annotation to the LibraryRepository constructor so Dagger knows how to create an instance of LibraryRepository. public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } } 2. Annotate Dependencies: Similarly, annotate the constructors of the dependencies (LocalLibraryDataSource and RemoteLibraryDataSource) so Dagger knows how to create them. public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } } 3. Define the Component: Create an interface annotated with @Component to define the dependency graph. @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); } When you build the project, Dagger generates an implementation of the ApplicationComponent interface for you, typically named DaggerApplicationComponent. Usage You can now use the generated component to obtain instances of your classes with their dependencies automatically injected: public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } } In your activity or fragment, you can retrieve the LibraryRepository instance: public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } } Key Concepts in Dagger 2 1. Modules ∘ Key Concepts of Modules ∘ Including Modules in Components 2. Scopes 3. Components 4. Component Dependencies 5. Runtime Bindings 1. Modules Modules in Dagger 2 are classes annotated with @Module that provide dependencies to the components. They contain methods annotated with @Provides or @Binds to specify how to create and supply dependencies. Modules are essential for organizing and managing the creation of objects that your application needs. Key Concepts of Modules @Module Annotation: This annotation is used to define a class as a Dagger module. A module class contains methods that provide dependencies. @Provides Annotation: This annotation is used on methods within a module to indicate that the method provides a certain dependency. These methods are responsible for creating and returning instances of the dependencies. @Binds Annotation: This annotation is used in abstract classes to bind an implementation to an interface. It is more concise than @Provides and is used when the module is an abstract class. Example of a Module @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } } In this example, NetworkModule is a class annotated with @Module. It contains two methods annotated with @Provides that create and return instances of Retrofit and OkHttpClient. Using @Binds When you have an interface and its implementation, you can use @Binds to bind the implementation to the interface. This is more concise than using @Provides. public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); } In this example, ApiModule is an abstract class annotated with @Module. The bindApiService method is annotated with @Binds to bind ApiServiceImpl to ApiService. Modules can be organized based on the functionality they provide. For example, you can have separate modules for network operations, database operations, and UI-related dependencies. Example: NetworkModule: Provides network-related dependencies like Retrofit and OkHttpClient. DatabaseModule: Provides database-related dependencies like RoomDatabase. UIModule: Provides UI-related dependencies like ViewModel and Presenter. Including Modules in Components Modules are included in components to provide dependencies to the classes that need them. Here’s how you can set it up: ApplicationComponent.java: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In this example, ApplicationComponent includes NetworkModule and DatabaseModule to provide dependencies to the application. 2. Scopes Scopes in Dagger 2 are annotations that define the lifecycle of dependencies. They ensure that a single instance of a dependency is created and shared within a specified scope. This helps in managing memory efficiently and ensuring that dependencies are reused where appropriate. Singleton Scope: Ensures a single instance of a dependency throughout the application’s lifecycle. Activity Scope: Ensures a single instance of a dependency within the lifecycle of an activity. Fragment Scope: Ensures a single instance of a dependency within the lifecycle of a fragment. 1. Singleton Scope Definition: The @Singleton scope ensures that a single instance of a dependency is created and shared throughout the entire application’s lifecycle. This scope is typically used for dependencies that need to be shared across the entire application, such as network clients, database instances, or shared preferences. Example: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In this example, the @Singleton annotation ensures that the Retrofit and Database instances provided by NetworkModule and DatabaseModule are singletons and shared across the entire application. 2. Activity Scope Definition: The @ActivityScope (a custom scope) ensures that a single instance of a dependency is created and shared within the lifecycle of an activity. This scope is useful for dependencies that are specific to an activity and should be recreated each time the activity is recreated, such as presenters or view models. Example: @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In this example, the @ActivityScope annotation ensures that dependencies provided by ActivityModule are scoped to the lifecycle of the activity. 3. Fragment Scope Definition: The @FragmentScope (another custom scope) ensures that a single instance of a dependency is created and shared within the lifecycle of a fragment. Use Case: This scope is useful for dependencies that are specific to a fragment and should be recreated each time the fragment is recreated, such as fragment-specific presenters or view models. Example: @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); } In this example, the @FragmentScope annotation ensures that dependencies provided by FragmentModule are scoped to the lifecycle of the fragment. 3. Components Component dependencies allow one component to depend on another, enabling the reuse of dependencies. There are two main types of component dependencies: Application Component: Provides dependencies that are needed throughout the entire application. Activity Component: Provides dependencies that are needed within a specific activity. 1. Application Component Definition: The Application Component provides dependencies that are needed throughout the entire application. It is typically scoped with @Singleton to ensure that the dependencies are shared across the application. This component is used for dependencies that need to be available globally, such as network clients, database instances, or shared preferences. Example: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In this example, the ApplicationComponent is responsible for providing Retrofit and Database instances, which are shared across the entire application. 2. Activity Component Definition: The Activity Component provides dependencies that are needed within a specific activity. It is typically scoped with a custom scope, such as @ActivityScope, to ensure that the dependencies are recreated each time the activity is recreated. This component is used for dependencies that are specific to an activity, such as presenters or view models. Example: @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In this example, the ActivityComponent depends on the ApplicationComponent and provides dependencies specific to the MainActivity. 4. Component Dependencies Component dependencies allow one component to depend on another, enabling the reuse of dependencies. There are two main types of component dependencies: Subcomponents: A subcomponent is a child of another component and can access its parent’s dependencies. Dependency Attribute: This allows a component to depend on another component without being a subcomponent. 1. Subcomponents: A subcomponent is a child of another component and can access its parent’s dependencies. Subcomponents are defined within the parent component and can inherit its scope. Example: @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); } In this example, ActivitySubcomponent is a subcomponent of the parent component and can access its dependencies. 2. Dependency Attribute This allows a component to depend on another component without being a subcomponent. The dependent component can access the dependencies provided by the parent component. Example: @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In this example, ActivityComponent depends on ApplicationComponent and can access its dependencies. 5. Runtime Bindings Runtime bindings in Dagger 2 refer to the provision of dependencies that are created and managed at runtime, based on the context in which they are needed. Application Context: Used for dependencies that need to live as long as the application. Activity Context: Used for dependencies that need to live as long as an activity. 1. Application Context Definition: The application context is a context that is tied to the lifecycle of the entire application. It is used for dependencies that need to live as long as the application itself. Dependencies are shared across the entire application and do not need to be recreated for each activity or fragment. Examples include network clients, database instances, and shared preferences. Example: @Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } } In this example, AppModule provides the application context as a singleton dependency. The provideApplicationContext method ensures that the context provided is tied to the lifecycle of the application. 2. Activity Context Definition: The activity context is a context that is tied to the lifecycle of a specific activity. It is used for dependencies that need to live as long as the activity itself. Dependencies that are specific to an activity and should be recreated each time the activity is recreated. Examples include view models, presenters, and UI-related dependencies. Example: @Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } } In this example, ActivityModule provides the activity context as a scoped dependency. The provideActivityContext method ensures that the context provided is tied to the lifecycle of the activity. Using Runtime Bindings in Components To use these runtime bindings, you need to include the corresponding modules in your components: Application Component: @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); } Activity Component: @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); } Injecting Contexts Once you have set up your components and modules, you can inject the contexts into your classes as needed. Example in an Activity: public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } } In this example, MainActivity receives both the activity context and the application context through dependency injection. This allows the activity to use the appropriate context based on the specific needs of the dependencies. Example: Using Dagger 2 in an Android Application Setting Up Dagger 2 To use Dagger 2 in your project, you need to add the following dependencies to your build.gradle file: dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' } Replace 2.x with the latest version of Dagger 2. Step 1: Define a Module Create a module to provide dependencies. For example, a NetworkModule to provide a Retrofit instance: @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } } Step 2: Define a Component Create a component to bridge the module and the classes that need the dependencies: @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } Step 3: Inject Dependencies Use the component to inject dependencies into your classes. For example, in your Application class: public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } } Step 4: Use Injected Dependencies Now, you can use the injected dependencies in your classes. For example, in an Activity: public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } } Conclusion Let’s summarize this topic: The main point of DI is to loosen coupling, making it easier to manage dependencies. By using DI, you can increase code flexibility and simplify the testing process. DI is a complex topic with different implementations based on the scenario. DI in different languages has peculiarities that can affect how you work with it. Dagger 2 automates the process of creating and providing dependencies, reducing boilerplate code and improving maintainability. Dagger 2 provides compile-time safety, ensuring that all dependencies are satisfied before the application runs. By generating code at compile time, Dagger 2 avoids the performance overhead associated with reflection-based solutions. Dependency Injection in Android Introduction to Dependency Injection Dependency Injection (DI) is a design pattern used to implement Inversion of Control (IoC) where the control of creating and managing dependencies is transferred from the application to an external entity. This helps in creating more modular, testable, and maintainable code. It is a technique where the responsibility of creating objects is transferred to other parts of the code. This promotes loose coupling, making the code more modular and easier to manage. Classes often need references to other classes to function properly. For instance, consider a Library class that requires a Book class. These necessary classes are known as dependencies. The Library class depends on having an instance of the Book class to operate. Library Book Library Book There are three primary ways for a class to obtain the objects it needs: Self-construction: The class creates and initializes its own dependencies. For example, the Library class would create and initialize its own instance of the Book class. External retrieval: The class retrieves dependencies from an external source. Some Android APIs, such as Context getters and getSystemService(), work this way. Dependency Injection: Dependencies are provided to the class, either when it is constructed or through methods that require them. For example, the Library constructor would receive a Book instance as a parameter. Self-construction: The class creates and initializes its own dependencies. For example, the Library class would create and initialize its own instance of the Book class. Self-construction : The class creates and initializes its own dependencies. For example, the Library class would create and initialize its own instance of the Book class. Self-construction Library Book External retrieval: The class retrieves dependencies from an external source. Some Android APIs, such as Context getters and getSystemService(), work this way. External retrieval : The class retrieves dependencies from an external source. Some Android APIs, such as Context getters and getSystemService() , work this way. External retrieval Context getSystemService() Dependency Injection: Dependencies are provided to the class, either when it is constructed or through methods that require them. For example, the Library constructor would receive a Book instance as a parameter. Dependency Injection : Dependencies are provided to the class, either when it is constructed or through methods that require them. For example, the Library constructor would receive a Book instance as a parameter. Dependency Injection Library Book The third option is dependency injection! With DI, you provide the dependencies of a class rather than having the class instance obtain them itself. Example Without Dependency Injection Without DI, a Library that creates its own Book dependency might look like this: Library Book class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } } class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } } This is not an example of DI because the Library class constructs its own Book . This can be problematic because: Library Book Tight coupling: Library and Book are tightly coupled. An instance of Library uses one type of Book, making it difficult to use subclasses or alternative implementations. Tight coupling : Library and Book are tightly coupled. An instance of Library uses one type of Book , making it difficult to use subclasses or alternative implementations. Tight coupling Library Book Library Book Testing difficulties: The hard dependency on Book makes testing more challenging. Library uses a real instance of Book, preventing the use of test doubles to modify Book for different test cases. Testing difficulties : The hard dependency on Book makes testing more challenging. Library uses a real instance of Book , preventing the use of test doubles to modify Book for different test cases. Testing difficulties Book Library Book Book Example With Dependency Injection With DI, instead of each instance of Library constructing its own Book object, it receives a Book object as a parameter in its constructor: Library Book Book class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); } class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); } The main function uses Library . Since Library depends on Book , the app creates an instance of Book and then uses it to construct an instance of Library . The benefits of this DI-based approach are: Library Library Book Book Library Reusability ofLibrary: You can pass in different implementations of Book to Library. For example, you might define a new subclass of Book called EBook that you want Library to use. With DI, you simply pass an instance of EBook to Library, and it works without any further changes. Reusability of Library : You can pass in different implementations of Book to Library . For example, you might define a new subclass of Book called EBook that you want Library to use. With DI, you simply pass an instance of EBook to Library , and it works without any further changes. Reusability of Library Book Library Book EBook Library EBook Library Easy testing ofLibrary: You can pass in test doubles to test different scenarios. Easy testing of Library : You can pass in test doubles to test different scenarios. Easy testing of Library Another DI Example Consider a scenario where a NotificationService class relies on a Notification class. Without DI, the NotificationService directly creates an instance of Notification , making it difficult to use different types of notifications or to test the service with various notification implementations. NotificationService Notification NotificationService Notification To illustrate DI, let’s refactor this example: interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } } interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } } Now, NotificationService depends on the Notification interface rather than a specific class. This allows different implementations of Notification to be used interchangeably. You can set the implementation you want to use through the sendNotification method: NotificationService Notification Notification sendNotification NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification()); NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification()); Methods of Dependency Injection in Android There are three main types of DI: Method (Interface) Injection: Dependencies are passed through methods that the class can access via an interface or another class. The previous example demonstrates method injection. Constructor Injection: Dependencies are passed to the class through its constructor. Method (Interface) Injection: Dependencies are passed through methods that the class can access via an interface or another class. The previous example demonstrates method injection. Method (Interface) Injection : Dependencies are passed through methods that the class can access via an interface or another class. The previous example demonstrates method injection. Method (Interface) Injection Constructor Injection: Dependencies are passed to the class through its constructor. Constructor Injection : Dependencies are passed to the class through its constructor. Constructor Injection class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } } class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } } 3. Field Injection (or Setter Injection) : Certain Android framework classes, such as activities and fragments, are instantiated by the system, so constructor injection is not possible. With field injection, dependencies are instantiated after the class is created. 3. Field Injection (or Setter Injection) class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } } class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } } 4. Method Injection : Dependencies are provided through methods, often using the @Inject annotation. 4. Method Injection @Inject Advantages of Dependency Injection Classes become more reusable and less dependent on specific implementations. This is due to the inversion of control, where classes no longer manage their dependencies but work with any configuration provided. Classes become more reusable and less dependent on specific implementations. This is due to the inversion of control, where classes no longer manage their dependencies but work with any configuration provided. Dependencies are part of the API surface and can be verified at object creation or compile time, making refactoring easier. Dependencies are part of the API surface and can be verified at object creation or compile time, making refactoring easier. Since a class does not manage its dependencies, different implementations can be passed during testing to cover various scenarios. Since a class does not manage its dependencies, different implementations can be passed during testing to cover various scenarios. Automated Dependency Injection In the previous example, you manually created, provided, and managed the dependencies of different classes without using a library. This approach is known as manual dependency injection. While it works for simple cases, it becomes cumbersome as the number of dependencies and classes increases. Manual dependency injection has several drawbacks: Boilerplate Code: For large applications, managing all dependencies and connecting them correctly can result in a lot of repetitive code. In a multi-layered architecture, creating an object for a top layer requires providing all dependencies for the layers below it. For instance, to build a computer, you need a CPU, a motherboard, RAM, and other components; and a CPU might need transistors and capacitors. Boilerplate Code : For large applications, managing all dependencies and connecting them correctly can result in a lot of repetitive code. In a multi-layered architecture, creating an object for a top layer requires providing all dependencies for the layers below it. For instance, to build a computer, you need a CPU, a motherboard, RAM, and other components; and a CPU might need transistors and capacitors. Boilerplate Code Complex Dependency Management: When you can’t construct dependencies beforehand — such as with lazy initializations or scoping objects to specific flows in your app — you need to write and maintain a custom container (or dependency graph) to manage the lifetimes of your dependencies in memory. Complex Dependency Management : When you can’t construct dependencies beforehand — such as with lazy initializations or scoping objects to specific flows in your app — you need to write and maintain a custom container (or dependency graph) to manage the lifetimes of your dependencies in memory. Complex Dependency Management Libraries can automate this process by creating and providing dependencies for you. These libraries fall into two categories: Reflection-based Solutions: These connect dependencies at runtime. Static Solutions: These generate code to connect dependencies at compile time. Reflection-based Solutions: These connect dependencies at runtime. Reflection-based Solutions : These connect dependencies at runtime. Reflection-based Solutions Static Solutions: These generate code to connect dependencies at compile time. Static Solutions : These generate code to connect dependencies at compile time. Static Solutions Dagger is a popular dependency injection library for Java, Kotlin, and Android, maintained by Google. Dagger simplifies DI in your app by creating and managing the dependency graph for you. It provides fully static, compile-time dependencies, addressing many of the development and performance issues associated with reflection-based solutions like Guice. Dagger is a popular dependency injection library for Java, Kotlin, and Android, maintained by Google. Dagger simplifies DI in your app by creating and managing the dependency graph for you. It provides fully static, compile-time dependencies, addressing many of the development and performance issues associated with reflection-based solutions like Guice. Reflection-based Solutions These frameworks connect dependencies at runtime: Toothpick: A runtime DI framework that uses reflection to connect dependencies. It’s designed to be lightweight and fast, making it suitable for Android applications. Toothpick : A runtime DI framework that uses reflection to connect dependencies. It’s designed to be lightweight and fast, making it suitable for Android applications. Toothpick Static Solutions These frameworks generate code to connect dependencies at compile time: Hilt: Built on top of Dagger, Hilt provides a standard way to incorporate Dagger dependency injection into an Android application. It simplifies the setup and usage of Dagger by providing predefined components and scopes. Koin: A lightweight and simple DI framework for Kotlin. Koin uses a DSL to define dependencies and is easy to set up and use. Kodein: A Kotlin-based DI framework that is easy to use and understand. It provides a simple and flexible API for managing dependencies. Hilt: Built on top of Dagger, Hilt provides a standard way to incorporate Dagger dependency injection into an Android application. It simplifies the setup and usage of Dagger by providing predefined components and scopes. Hilt : Built on top of Dagger, Hilt provides a standard way to incorporate Dagger dependency injection into an Android application. It simplifies the setup and usage of Dagger by providing predefined components and scopes . Hilt It simplifies the setup and usage of Dagger by providing predefined components and scopes Koin: A lightweight and simple DI framework for Kotlin. Koin uses a DSL to define dependencies and is easy to set up and use. Koin : A lightweight and simple DI framework for Kotlin. Koin uses a DSL to define dependencies and is easy to set up and use. Koin Kodein: A Kotlin-based DI framework that is easy to use and understand. It provides a simple and flexible API for managing dependencies. Kodein : A Kotlin-based DI framework that is easy to use and understand. It provides a simple and flexible API for managing dependencies. Kodein Alternatives to Dependency Injection An alternative to dependency injection is the service locator pattern. This design pattern also helps decouple classes from their concrete dependencies. You create a class known as the service locator that creates and stores dependencies, providing them on demand. object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() } object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() } The service locator pattern differs from dependency injection in how dependencies are consumed. With the service locator pattern, classes request the dependencies they need; with dependency injection, the app proactively provides the required objects. What is Dagger 2? Dagger 2 is a popular DI framework for Android. It uses compile-time code generation and is known for its high performance. Dagger 2 simplifies the process of dependency injection by generating the necessary code to handle dependencies, reducing boilerplate and improving efficiency. Dagger 2 is an annotation-based library for dependency injection in Android. Here are the key annotations and their purposes: @Module: Used to define classes that provide dependencies. For example, a module can provide an ApiClient for Retrofit. @Provides: Annotates methods in a module to specify how to create and return dependencies. @Inject: Used to request dependencies. Can be applied to fields, constructors, and methods. @Component: An interface that bridges @Module and @Inject. It contains all the modules and provides the builder for the application. @Singleton: Ensures a single instance of a dependency is created. @Binds: Used in abstract classes to provide dependencies, similar to @Provides but more concise. @Module : Used to define classes that provide dependencies. For example, a module can provide an ApiClient for Retrofit. @Module ApiClient @Provides : Annotates methods in a module to specify how to create and return dependencies. @Provides @Inject : Used to request dependencies. Can be applied to fields, constructors, and methods. @Inject @Component : An interface that bridges @Module and @Inject . It contains all the modules and provides the builder for the application. @Component @Module @Inject @Singleton : Ensures a single instance of a dependency is created. @Singleton @Binds : Used in abstract classes to provide dependencies, similar to @Provides but more concise. @Binds @Provides Dagger Components Dagger can generate a dependency graph for your project, allowing it to determine where to obtain dependencies when needed. To enable this, you need to create an interface and annotate it with @Component . @Component Within the @Component interface, you define methods that return instances of the classes you need (e.g., BookRepository ). The @Component annotation instructs Dagger to generate a container with all the dependencies required to satisfy the types it exposes. This container is known as a Dagger component, and it contains a graph of objects that Dagger knows how to provide along with their dependencies. @Component BookRepository @Component Example Let’s consider an example involving a LibraryRepository : LibraryRepository Annotate the Constructor: Add an @Inject annotation to the LibraryRepository constructor so Dagger knows how to create an instance of LibraryRepository. Annotate the Constructor : Add an @Inject annotation to the LibraryRepository constructor so Dagger knows how to create an instance of LibraryRepository . Annotate the Constructor @Inject LibraryRepository LibraryRepository public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } } public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } } 2. Annotate Dependencies : Similarly, annotate the constructors of the dependencies ( LocalLibraryDataSource and RemoteLibraryDataSource ) so Dagger knows how to create them. 2. Annotate Dependencies LocalLibraryDataSource RemoteLibraryDataSource public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } } public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } } 3. Define the Component : Create an interface annotated with @Component to define the dependency graph. 3. Define the Component @Component @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); } @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); } When you build the project, Dagger generates an implementation of the ApplicationComponent interface for you, typically named DaggerApplicationComponent . ApplicationComponent DaggerApplicationComponent Usage You can now use the generated component to obtain instances of your classes with their dependencies automatically injected: public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } } public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } } In your activity or fragment, you can retrieve the LibraryRepository instance: LibraryRepository public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } } public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } } Key Concepts in Dagger 2 1. Modules ∘ Key Concepts of Modules ∘ Including Modules in Components 2. Scopes 3. Components 4. Component Dependencies 5. Runtime Bindings 1. Modules Modules in Dagger 2 are classes annotated with @Module that provide dependencies to the components. They contain methods annotated with @Provides or @Binds to specify how to create and supply dependencies. Modules are essential for organizing and managing the creation of objects that your application needs. @Module @Provides @Binds Key Concepts of Modules @Module Annotation: This annotation is used to define a class as a Dagger module. A module class contains methods that provide dependencies. @Provides Annotation: This annotation is used on methods within a module to indicate that the method provides a certain dependency. These methods are responsible for creating and returning instances of the dependencies. @Binds Annotation: This annotation is used in abstract classes to bind an implementation to an interface. It is more concise than @Provides and is used when the module is an abstract class. @Module Annotation: This annotation is used to define a class as a Dagger module. A module class contains methods that provide dependencies. @Module Annotation: This annotation is used to define a class as a Dagger module. A module class contains methods that provide dependencies. @Provides Annotation: This annotation is used on methods within a module to indicate that the method provides a certain dependency. These methods are responsible for creating and returning instances of the dependencies. @Provides Annotation: This annotation is used on methods within a module to indicate that the method provides a certain dependency. These methods are responsible for creating and returning instances of the dependencies. @Binds Annotation: This annotation is used in abstract classes to bind an implementation to an interface. It is more concise than @Provides and is used when the module is an abstract class. @Binds Annotation: This annotation is used in abstract classes to bind an implementation to an interface. It is more concise than @Provides and is used when the module is an abstract class. @Provides Example of a Module Example of a Module @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } } @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } } In this example, NetworkModule is a class annotated with @Module . It contains two methods annotated with @Provides that create and return instances of Retrofit and OkHttpClient . NetworkModule @Module @Provides Retrofit OkHttpClient Using @Binds Using @Binds When you have an interface and its implementation, you can use @Binds to bind the implementation to the interface. This is more concise than using @Provides . @Binds @Provides public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); } public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); } In this example, ApiModule is an abstract class annotated with @Module . The bindApiService method is annotated with @Binds to bind ApiServiceImpl to ApiService . ApiModule @Module bindApiService @Binds ApiServiceImpl ApiService Modules can be organized based on the functionality they provide. For example, you can have separate modules for network operations, database operations, and UI-related dependencies. Example: NetworkModule: Provides network-related dependencies like Retrofit and OkHttpClient. DatabaseModule: Provides database-related dependencies like RoomDatabase. UIModule: Provides UI-related dependencies like ViewModel and Presenter. NetworkModule : Provides network-related dependencies like Retrofit and OkHttpClient . NetworkModule Retrofit OkHttpClient DatabaseModule : Provides database-related dependencies like RoomDatabase . DatabaseModule RoomDatabase UIModule : Provides UI-related dependencies like ViewModel and Presenter . UIModule ViewModel Presenter Including Modules in Components Modules are included in components to provide dependencies to the classes that need them. Here’s how you can set it up: ApplicationComponent.java: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In this example, ApplicationComponent includes NetworkModule and DatabaseModule to provide dependencies to the application. ApplicationComponent NetworkModule DatabaseModule 2. Scopes Scopes in Dagger 2 are annotations that define the lifecycle of dependencies. They ensure that a single instance of a dependency is created and shared within a specified scope. This helps in managing memory efficiently and ensuring that dependencies are reused where appropriate. Singleton Scope: Ensures a single instance of a dependency throughout the application’s lifecycle. Activity Scope: Ensures a single instance of a dependency within the lifecycle of an activity. Fragment Scope: Ensures a single instance of a dependency within the lifecycle of a fragment. Singleton Scope : Ensures a single instance of a dependency throughout the application’s lifecycle. Singleton Scope Activity Scope : Ensures a single instance of a dependency within the lifecycle of an activity. Activity Scope Fragment Scope : Ensures a single instance of a dependency within the lifecycle of a fragment. Fragment Scope 1. Singleton Scope 1. Singleton Scope 1. Singleton Scope Definition : The @Singleton scope ensures that a single instance of a dependency is created and shared throughout the entire application’s lifecycle. Definition @Singleton This scope is typically used for dependencies that need to be shared across the entire application, such as network clients, database instances, or shared preferences. Example: Example: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In this example, the @Singleton annotation ensures that the Retrofit and Database instances provided by NetworkModule and DatabaseModule are singletons and shared across the entire application. @Singleton Retrofit Database NetworkModule DatabaseModule 2. Activity Scope 2. Activity Scope 2. Activity Scope Definition : The @ActivityScope (a custom scope) ensures that a single instance of a dependency is created and shared within the lifecycle of an activity. Definition @ActivityScope This scope is useful for dependencies that are specific to an activity and should be recreated each time the activity is recreated, such as presenters or view models. Example : Example @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In this example, the @ActivityScope annotation ensures that dependencies provided by ActivityModule are scoped to the lifecycle of the activity. @ActivityScope ActivityModule 3. Fragment Scope 3. Fragment Scope 3. Fragment Scope Definition : The @FragmentScope (another custom scope) ensures that a single instance of a dependency is created and shared within the lifecycle of a fragment. Definition @FragmentScope Use Case: This scope is useful for dependencies that are specific to a fragment and should be recreated each time the fragment is recreated, such as fragment-specific presenters or view models. Example : Example @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); } @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); } In this example, the @FragmentScope annotation ensures that dependencies provided by FragmentModule are scoped to the lifecycle of the fragment. @FragmentScope FragmentModule 3. Components Component dependencies allow one component to depend on another, enabling the reuse of dependencies. There are two main types of component dependencies: Application Component: Provides dependencies that are needed throughout the entire application. Activity Component: Provides dependencies that are needed within a specific activity. Application Component : Provides dependencies that are needed throughout the entire application. Application Component Activity Component : Provides dependencies that are needed within a specific activity. Activity Component 1. Application Component 1. Application Component 1. Application Component Definition : The Application Component provides dependencies that are needed throughout the entire application. It is typically scoped with @Singleton to ensure that the dependencies are shared across the application. Definition @Singleton This component is used for dependencies that need to be available globally, such as network clients, database instances, or shared preferences. Example : Example @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In this example, the ApplicationComponent is responsible for providing Retrofit and Database instances, which are shared across the entire application. ApplicationComponent Retrofit Database 2. Activity Component 2. Activity Component 2. Activity Component Definition : The Activity Component provides dependencies that are needed within a specific activity. It is typically scoped with a custom scope, such as @ActivityScope , to ensure that the dependencies are recreated each time the activity is recreated. Definition @ActivityScope This component is used for dependencies that are specific to an activity, such as presenters or view models. Example : Example @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In this example, the ActivityComponent depends on the ApplicationComponent and provides dependencies specific to the MainActivity . ActivityComponent ApplicationComponent MainActivity 4. Component Dependencies Component dependencies allow one component to depend on another, enabling the reuse of dependencies. There are two main types of component dependencies: Subcomponents: A subcomponent is a child of another component and can access its parent’s dependencies. Dependency Attribute: This allows a component to depend on another component without being a subcomponent. Subcomponents : A subcomponent is a child of another component and can access its parent’s dependencies. Subcomponents Dependency Attribute : This allows a component to depend on another component without being a subcomponent. Dependency Attribute 1. Subcomponents: 1. Subcomponents: 1. Subcomponents: A subcomponent is a child of another component and can access its parent’s dependencies. Subcomponents are defined within the parent component and can inherit its scope. Example : Example @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); } @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); } In this example, ActivitySubcomponent is a subcomponent of the parent component and can access its dependencies. ActivitySubcomponent 2. Dependency Attribute 2. Dependency Attribute 2. Dependency Attribute This allows a component to depend on another component without being a subcomponent. The dependent component can access the dependencies provided by the parent component. Example : Example @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In this example, ActivityComponent depends on ApplicationComponent and can access its dependencies. ActivityComponent ApplicationComponent 5. Runtime Bindings Runtime bindings in Dagger 2 refer to the provision of dependencies that are created and managed at runtime, based on the context in which they are needed. Application Context: Used for dependencies that need to live as long as the application. Activity Context: Used for dependencies that need to live as long as an activity. Application Context : Used for dependencies that need to live as long as the application. Application Context Activity Context : Used for dependencies that need to live as long as an activity. Activity Context 1. Application Context 1. Application Context 1. Application Context Definition : The application context is a context that is tied to the lifecycle of the entire application. It is used for dependencies that need to live as long as the application itself. Definition Dependencies are shared across the entire application and do not need to be recreated for each activity or fragment. Examples include network clients, database instances, and shared preferences. Example : Example @Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } } @Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } } In this example, AppModule provides the application context as a singleton dependency. The provideApplicationContext method ensures that the context provided is tied to the lifecycle of the application. AppModule provideApplicationContext 2. Activity Context 2. Activity Context 2. Activity Context Definition : The activity context is a context that is tied to the lifecycle of a specific activity. It is used for dependencies that need to live as long as the activity itself. Definition Dependencies that are specific to an activity and should be recreated each time the activity is recreated. Examples include view models, presenters, and UI-related dependencies. Example : Example @Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } } @Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } } In this example, ActivityModule provides the activity context as a scoped dependency. The provideActivityContext method ensures that the context provided is tied to the lifecycle of the activity. ActivityModule provideActivityContext Using Runtime Bindings in Components To use these runtime bindings, you need to include the corresponding modules in your components: Application Component : Application Component @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); } @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); } Activity Component : Activity Component @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); } Injecting Contexts Injecting Contexts Once you have set up your components and modules, you can inject the contexts into your classes as needed. Example in an Activity : Example in an Activity public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } } public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } } In this example, MainActivity receives both the activity context and the application context through dependency injection. This allows the activity to use the appropriate context based on the specific needs of the dependencies. MainActivity Example: Using Dagger 2 in an Android Application Setting Up Dagger 2 To use Dagger 2 in your project, you need to add the following dependencies to your build.gradle file: build.gradle dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' } dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' } Replace 2.x with the latest version of Dagger 2. 2.x Step 1: Define a Module Create a module to provide dependencies. For example, a NetworkModule to provide a Retrofit instance: NetworkModule Retrofit @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } } @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } } Step 2: Define a Component Create a component to bridge the module and the classes that need the dependencies: @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } Step 3: Inject Dependencies Use the component to inject dependencies into your classes. For example, in your Application class: Application public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } } public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } } Step 4: Use Injected Dependencies Now, you can use the injected dependencies in your classes. For example, in an Activity : Activity public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } } public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } } Conclusion Let’s summarize this topic: The main point of DI is to loosen coupling, making it easier to manage dependencies. By using DI, you can increase code flexibility and simplify the testing process. DI is a complex topic with different implementations based on the scenario. DI in different languages has peculiarities that can affect how you work with it. Dagger 2 automates the process of creating and providing dependencies, reducing boilerplate code and improving maintainability. Dagger 2 provides compile-time safety, ensuring that all dependencies are satisfied before the application runs. By generating code at compile time, Dagger 2 avoids the performance overhead associated with reflection-based solutions. The main point of DI is to loosen coupling, making it easier to manage dependencies. By using DI, you can increase code flexibility and simplify the testing process. DI is a complex topic with different implementations based on the scenario. DI in different languages has peculiarities that can affect how you work with it. Dagger 2 automates the process of creating and providing dependencies, reducing boilerplate code and improving maintainability. Dagger 2 provides compile-time safety, ensuring that all dependencies are satisfied before the application runs. By generating code at compile time, Dagger 2 avoids the performance overhead associated with reflection-based solutions.