Introducción a la inyección de dependencia La inyección de dependencias (DI) es un patrón de diseño que se utiliza para implementar la inversión de control (IoC), en el que el control de la creación y la gestión de dependencias se transfiere de la aplicación a una entidad externa. Esto ayuda a crear un código más modular, comprobable y fácil de mantener. Es una técnica en la que la responsabilidad de crear objetos se transfiere a otras partes del código. Esto promueve un acoplamiento flexible, lo que hace que el código sea más modular y más fácil de gestionar. Las clases a menudo necesitan referencias a otras clases para funcionar correctamente. Por ejemplo, considere una clase que requiere una clase . Estas clases necesarias se conocen como dependencias. La clase depende de tener una instancia de la clase para funcionar. Library Book Library Book Hay tres formas principales para que una clase obtenga los objetos que necesita: : la clase crea e inicializa sus propias dependencias. Por ejemplo, la clase crearía e inicializaría su propia instancia de la clase . Autoconstrucción Library Book : la clase recupera dependencias de una fuente externa. Algunas API de Android, como los captadores y , funcionan de esta manera. Recuperación externa Context getSystemService() : las dependencias se proporcionan a la clase, ya sea cuando se construye o a través de métodos que las requieren. Por ejemplo, el constructor recibiría una instancia como parámetro. Inyección de dependencias Library Book La tercera opción es la inyección de dependencias. Con la inyección de dependencias, se proporcionan las dependencias de una clase en lugar de que la instancia de la clase las obtenga por sí sola. Ejemplo sin inyección de dependencia Sin DI, una que crea su propia dependencia podría verse así: 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(); } } Este no es un ejemplo de DI porque la clase construye su propio . Esto puede ser problemático porque: Library Book : y están acoplados estrechamente. Una instancia de utiliza un tipo de , lo que dificulta el uso de subclases o implementaciones alternativas. Acoplamiento estrecho Library Book Library Book : la dependencia estricta de hace que las pruebas sean más desafiantes. usa una instancia real de , lo que evita el uso de dobles de prueba para modificar para diferentes casos de prueba. Dificultades de prueba Book Library Book Book Ejemplo con inyección de dependencia Con DI, en lugar de que cada instancia de construya su propio objeto , recibe un objeto como parámetro en su 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(); } La función principal utiliza . Dado que depende de , la aplicación crea una instancia de y luego la utiliza para construir una instancia de . Los beneficios de este enfoque basado en DI son: Library Library Book Book Library : puedes pasar diferentes implementaciones de a . Por ejemplo, puedes definir una nueva subclase de llamada que quieres que use. Con DI, simplemente pasas una instancia de a y funciona sin más cambios. Reutilización de Library Book Library Book EBook Library EBook Library : puedes pasar pruebas dobles para probar diferentes escenarios. Prueba fácil de Library Otro ejemplo de DI Considere un escenario en el que una clase depende de una clase . Sin DI, crea directamente una instancia de , lo que dificulta el uso de diferentes tipos de notificaciones o la prueba del servicio con varias implementaciones de notificaciones. NotificationService Notification NotificationService Notification Para ilustrar DI, refactoricemos este ejemplo: 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(); } } Ahora, depende de la interfaz en lugar de una clase específica. Esto permite que se utilicen de forma intercambiable distintas implementaciones de . Puede configurar la implementación que desea utilizar a través del método : NotificationService Notification Notification sendNotification NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification()); Métodos de inyección de dependencia en Android Hay tres tipos principales de DI: : las dependencias se pasan a través de métodos a los que la clase puede acceder mediante una interfaz u otra clase. El ejemplo anterior demuestra la inyección de método. Inyección de método (interfaz) : las dependencias se pasan a la clase a través de su constructor. Inyección de 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(); } } : el sistema crea instancias de ciertas clases del marco de Android, como actividades y fragmentos, por lo que no es posible la inyección de constructor. Con la inyección de campo, las dependencias se crean después de que se crea la clase. 3. Inyección de campo (o inyección de setter) 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(); } } : las dependencias se proporcionan a través de métodos, a menudo utilizando la anotación . 4. Inyección de métodos @Inject Ventajas de la inyección de dependencia Las clases se vuelven más reutilizables y menos dependientes de implementaciones específicas. Esto se debe a la inversión de control, donde las clases ya no administran sus dependencias sino que funcionan con cualquier configuración proporcionada. Las dependencias son parte de la superficie de la API y se pueden verificar en el momento de la creación del objeto o de la compilación, lo que facilita la refactorización. Dado que una clase no administra sus dependencias, se pueden pasar diferentes implementaciones durante las pruebas para cubrir distintos escenarios. Inyección de dependencia automatizada En el ejemplo anterior, creaste, proporcionaste y administraste manualmente las dependencias de diferentes clases sin usar una biblioteca. Este enfoque se conoce como inyección de dependencias manual. Si bien funciona para casos simples, se vuelve engorroso a medida que aumenta la cantidad de dependencias y clases. La inyección de dependencias manual tiene varias desventajas: : en el caso de aplicaciones de gran tamaño, gestionar todas las dependencias y conectarlas correctamente puede generar una gran cantidad de código repetitivo. En una arquitectura de varias capas, la creación de un objeto para una capa superior requiere proporcionar todas las dependencias para las capas inferiores. Por ejemplo, para construir una computadora, se necesita una CPU, una placa base, RAM y otros componentes; y una CPU puede necesitar transistores y capacitores. Código repetitivo : cuando no puedes construir dependencias de antemano (como con inicializaciones perezosas o con el alcance de objetos en flujos específicos en tu aplicación), necesitas escribir y mantener un contenedor personalizado (o gráfico de dependencias) para administrar la duración de vida de tus dependencias en la memoria. Gestión de dependencias complejas Las bibliotecas pueden automatizar este proceso creando y proporcionando dependencias. Estas bibliotecas se dividen en dos categorías: : conectan dependencias en tiempo de ejecución. Soluciones basadas en reflexión : generan código para conectar dependencias en tiempo de compilación. Soluciones estáticas Dagger es una biblioteca de inyección de dependencias popular para Java, Kotlin y Android, mantenida por Google. Dagger simplifica la inyección de dependencias en su aplicación al crear y administrar el gráfico de dependencias por usted. Proporciona dependencias totalmente estáticas en tiempo de compilación, lo que soluciona muchos de los problemas de desarrollo y rendimiento asociados con soluciones basadas en reflexión como Guice. Soluciones basadas en la reflexión Estos marcos conectan dependencias en tiempo de ejecución: : un marco de ejecución DI que utiliza la reflexión para conectar dependencias. Está diseñado para ser liviano y rápido, lo que lo hace adecuado para aplicaciones Android. Toothpick Soluciones estáticas Estos marcos generan código para conectar dependencias en tiempo de compilación: : Hilt, desarrollado sobre Dagger, ofrece una forma estándar de incorporar la inyección de dependencia de Dagger en una aplicación Android. . Hilt Simplifica la configuración y el uso de Dagger al proporcionar componentes y ámbitos predefinidos : un marco de DI ligero y simple para Kotlin. Koin utiliza un DSL para definir dependencias y es fácil de configurar y usar. Koin : un marco de DI basado en Kotlin que es fácil de usar y comprender. Proporciona una API simple y flexible para administrar dependencias. Kodein Alternativas a la inyección de dependencia Una alternativa a la inyección de dependencias es el patrón localizador de servicios. Este patrón de diseño también ayuda a desacoplar las clases de sus dependencias concretas. Se crea una clase conocida como localizador de servicios que crea y almacena dependencias, y las proporciona a pedido. 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() } El patrón de localizador de servicios difiere de la inyección de dependencias en la forma en que se consumen las dependencias. Con el patrón de localizador de servicios, las clases solicitan las dependencias que necesitan; con la inyección de dependencias, la aplicación proporciona de manera proactiva los objetos requeridos. ¿Qué es Dagger 2? Dagger 2 es un popular framework DI para Android. Utiliza generación de código en tiempo de compilación y es conocido por su alto rendimiento. Dagger 2 simplifica el proceso de inyección de dependencias al generar el código necesario para manejar las dependencias, reduciendo el código repetitivo y mejorando la eficiencia. Dagger 2 es una biblioteca basada en anotaciones para la inyección de dependencias en Android. Estas son las anotaciones clave y sus propósitos: : se utiliza para definir clases que proporcionan dependencias. Por ejemplo, un módulo puede proporcionar un para Retrofit. @Module ApiClient : anota métodos en un módulo para especificar cómo crear y devolver dependencias. @Provides : se utiliza para solicitar dependencias. Se puede aplicar a campos, constructores y métodos. @Inject : una interfaz que une y . Contiene todos los módulos y proporciona el generador para la aplicación. @Component @Module @Inject : garantiza que se cree una única instancia de una dependencia. @Singleton : se utiliza en clases abstractas para proporcionar dependencias, similar a pero más conciso. @Binds @Provides Componentes de la daga Dagger puede generar un gráfico de dependencias para su proyecto, lo que le permite determinar dónde obtener las dependencias cuando sea necesario. Para habilitar esto, debe crear una interfaz y anotarla con . @Component Dentro de la interfaz , se definen métodos que devuelven instancias de las clases que se necesitan (por ejemplo, ). La anotación indica a Dagger que genere un contenedor con todas las dependencias necesarias para satisfacer los tipos que expone. Este contenedor se conoce como componente Dagger y contiene un gráfico de objetos que Dagger sabe cómo proporcionar junto con sus dependencias. @Component BookRepository @Component Ejemplo Consideremos un ejemplo que involucra un : LibraryRepository : agregue una anotación al constructor para que Dagger sepa cómo crear una instancia de . Anotar el 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; } } : de manera similar, anote los constructores de las dependencias ( y ) para que Dagger sepa cómo crearlas. 2. Anotar dependencias 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; } } : cree una interfaz anotada con para definir el gráfico de dependencia. 3. Defina el componente @Component @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); } Cuando crea el proyecto, Dagger genera una implementación de la interfaz para usted, normalmente llamada . ApplicationComponent DaggerApplicationComponent Uso Ahora puedes usar el componente generado para obtener instancias de tus clases con sus dependencias inyectadas automáticamente: public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } } En su actividad o fragmento, puede recuperar la instancia : 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 } } Conceptos clave de Dagger 2 1. Módulos ∘ Conceptos clave de los módulos ∘ Inclusión de módulos en componentes 2. Ámbitos 3. Componentes 4. Dependencias de componentes 5. Enlaces en tiempo de ejecución 1. Módulos Los módulos en Dagger 2 son clases anotadas con que proporcionan dependencias a los componentes. Contienen métodos anotados con o para especificar cómo crear y proporcionar dependencias. Los módulos son esenciales para organizar y administrar la creación de objetos que necesita su aplicación. @Module @Provides @Binds Conceptos clave de los módulos Anotación @Module: esta anotación se utiliza para definir una clase como un módulo Dagger. Una clase de módulo contiene métodos que proporcionan dependencias. Anotación @Provides: esta anotación se utiliza en los métodos dentro de un módulo para indicar que el método proporciona una determinada dependencia. Estos métodos son responsables de crear y devolver instancias de las dependencias. Anotación @Binds: esta anotación se utiliza en clases abstractas para vincular una implementación a una interfaz. Es más concisa que y se utiliza cuando el módulo es una clase abstracta. @Provides Ejemplo de un módulo @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(); } } En este ejemplo, es una clase anotada con . Contiene dos métodos anotados con que crean y devuelven instancias de y . NetworkModule @Module @Provides Retrofit OkHttpClient Usando @Binds Cuando tienes una interfaz y su implementación, puedes usar para vincular la implementación a la interfaz. Esto es más conciso que usar . @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); } En este ejemplo, es una clase abstracta anotada con . El método está anotado con para vincular a . ApiModule @Module bindApiService @Binds ApiServiceImpl ApiService Los módulos se pueden organizar en función de la funcionalidad que brindan. Por ejemplo, puede tener módulos separados para operaciones de red, operaciones de base de datos y dependencias relacionadas con la interfaz de usuario. Ejemplo: : proporciona dependencias relacionadas con la red como y . NetworkModule Retrofit OkHttpClient : proporciona dependencias relacionadas con la base de datos como . DatabaseModule RoomDatabase : proporciona dependencias relacionadas con la interfaz de usuario, como y . UIModule ViewModel Presenter Inclusión de módulos en componentes Los módulos se incluyen en los componentes para proporcionar dependencias a las clases que los necesitan. A continuación, se muestra cómo configurarlo: Componente de aplicación.java: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } En este ejemplo, incluye y para proporcionar dependencias a la aplicación. ApplicationComponent NetworkModule DatabaseModule 2. Ámbitos Los ámbitos en Dagger 2 son anotaciones que definen el ciclo de vida de las dependencias. Garantizan que se cree y comparta una única instancia de una dependencia dentro de un ámbito específico. Esto ayuda a gestionar la memoria de manera eficiente y a garantizar que las dependencias se reutilicen cuando corresponda. : garantiza una única instancia de una dependencia durante todo el ciclo de vida de la aplicación. Ámbito Singleton : garantiza una única instancia de una dependencia dentro del ciclo de vida de una actividad. Ámbito de la actividad : garantiza una única instancia de una dependencia dentro del ciclo de vida de un fragmento. Alcance del fragmento 1. Alcance Singleton : El ámbito garantiza que se cree y comparta una única instancia de una dependencia durante todo el ciclo de vida de la aplicación. Definición @Singleton Este ámbito se utiliza normalmente para dependencias que deben compartirse en toda la aplicación, como clientes de red, instancias de bases de datos o preferencias compartidas. Ejemplo: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } En este ejemplo, la anotación garantiza que las instancias de y proporcionadas por y sean singletons y se compartan en toda la aplicación. @Singleton Retrofit Database NetworkModule DatabaseModule 2. Ámbito de actividad : (un ámbito personalizado) garantiza que se cree y comparta una única instancia de una dependencia dentro del ciclo de vida de una actividad. Definición @ActivityScope Este alcance es útil para las dependencias que son específicas de una actividad y deben recrearse cada vez que se recrea la actividad, como presentadores o modelos de vista. : Ejemplo @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } En este ejemplo, la anotación garantiza que las dependencias proporcionadas por estén limitadas al ciclo de vida de la actividad. @ActivityScope ActivityModule 3. Alcance del fragmento : (otro ámbito personalizado) garantiza que se cree y comparta una única instancia de una dependencia dentro del ciclo de vida de un fragmento. Definición @FragmentScope Caso de uso: este ámbito es útil para las dependencias que son específicas de un fragmento y deben recrearse cada vez que se recrea el fragmento, como presentadores o modelos de vista específicos del fragmento. : Ejemplo @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); } En este ejemplo, la anotación garantiza que las dependencias proporcionadas por tengan como alcance el ciclo de vida del fragmento. @FragmentScope FragmentModule 3. Componentes Las dependencias de componentes permiten que un componente dependa de otro, lo que permite la reutilización de dependencias. Existen dos tipos principales de dependencias de componentes: : proporciona dependencias necesarias en toda la aplicación. Componente de aplicación : proporciona dependencias necesarias dentro de una actividad específica. Componente de actividad 1. Componente de aplicación : El componente de aplicación proporciona dependencias que son necesarias en toda la aplicación. Normalmente, se le asigna el alcance para garantizar que las dependencias se compartan en toda la aplicación. Definición @Singleton Este componente se utiliza para dependencias que deben estar disponibles globalmente, como clientes de red, instancias de bases de datos o preferencias compartidas. : Ejemplo @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } En este ejemplo, es responsable de proporcionar instancias de y , que se comparten en toda la aplicación. ApplicationComponent Retrofit Database 2. Componente de actividad : El componente de actividad proporciona dependencias que son necesarias dentro de una actividad específica. Normalmente, tiene un alcance personalizado, como , para garantizar que las dependencias se vuelvan a crear cada vez que se vuelve a crear la actividad. Definición @ActivityScope Este componente se utiliza para dependencias que son específicas de una actividad, como presentadores o modelos de vista. : Ejemplo @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } En este ejemplo, depende de y proporciona dependencias específicas de . ActivityComponent ApplicationComponent MainActivity 4. Dependencias de componentes Las dependencias de componentes permiten que un componente dependa de otro, lo que permite la reutilización de dependencias. Existen dos tipos principales de dependencias de componentes: : un subcomponente es un hijo de otro componente y puede acceder a las dependencias de su padre. Subcomponentes : permite que un componente dependa de otro componente sin ser un subcomponente. Atributo de dependencia 1. Subcomponentes: Un subcomponente es un componente secundario de otro componente y puede acceder a las dependencias de su componente principal. Los subcomponentes se definen dentro del componente principal y pueden heredar su alcance. : Ejemplo @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); } En este ejemplo, es un subcomponente del componente principal y puede acceder a sus dependencias. ActivitySubcomponent 2. Atributo de dependencia Esto permite que un componente dependa de otro componente sin ser un subcomponente. El componente dependiente puede acceder a las dependencias proporcionadas por el componente principal. : Ejemplo @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } En este ejemplo, depende de y puede acceder a sus dependencias. ActivityComponent ApplicationComponent 5. Enlaces en tiempo de ejecución Los enlaces de tiempo de ejecución en Dagger 2 se refieren a la provisión de dependencias que se crean y administran en tiempo de ejecución, según el contexto en el que se necesitan. : se utiliza para dependencias que deben durar tanto como la aplicación. Contexto de aplicación : se utiliza para dependencias que necesitan durar tanto como una actividad. Contexto de actividad 1. Contexto de aplicación : El contexto de la aplicación es un contexto que está vinculado al ciclo de vida de toda la aplicación. Se utiliza para dependencias que deben durar tanto como la aplicación misma. Definición Dependencias que se comparten en toda la aplicación y que no es necesario volver a crear para cada actividad o fragmento. Algunos ejemplos son los clientes de red, las instancias de base de datos y las preferencias compartidas. : Ejemplo @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(); } } En este ejemplo, proporciona el contexto de la aplicación como una dependencia singleton. El método garantiza que el contexto proporcionado esté vinculado al ciclo de vida de la aplicación. AppModule provideApplicationContext 2. Contexto de la actividad : El contexto de actividad es un contexto que está vinculado al ciclo de vida de una actividad específica. Se utiliza para dependencias que deben durar tanto como la actividad misma. Definición Dependencias que son específicas de una actividad y que se deben volver a crear cada vez que se vuelve a crear la actividad. Algunos ejemplos son los modelos de vista, los presentadores y las dependencias relacionadas con la interfaz de usuario. : Ejemplo @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; } } En este ejemplo, proporciona el contexto de la actividad como una dependencia con alcance. El método garantiza que el contexto proporcionado esté vinculado al ciclo de vida de la actividad. ActivityModule provideActivityContext Uso de enlaces de tiempo de ejecución en componentes Para utilizar estos enlaces de tiempo de ejecución, debe incluir los módulos correspondientes en sus componentes: : Componente de aplicación @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); } : Componente de actividad @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); } Inyección de contextos Una vez que haya configurado sus componentes y módulos, puede inyectar los contextos en sus clases según sea necesario. : Ejemplo en una actividad 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); } } En este ejemplo, recibe tanto el contexto de la actividad como el de la aplicación a través de la inyección de dependencias. Esto permite que la actividad utilice el contexto adecuado en función de las necesidades específicas de las dependencias. MainActivity Ejemplo: uso de Dagger 2 en una aplicación de Android Configuración de Dagger 2 Para usar Dagger 2 en su proyecto, debe agregar las siguientes dependencias a su archivo : build.gradle dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' } Reemplace con la última versión de Dagger 2. 2.x Paso 1: Definir un módulo Cree un módulo para proporcionar dependencias. Por ejemplo, un para proporcionar una instancia : NetworkModule Retrofit @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } } Paso 2: Definir un componente Cree un componente para unir el módulo y las clases que necesitan las dependencias: @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } Paso 3: Inyectar dependencias Utilice el componente para inyectar dependencias en sus clases. Por ejemplo, en su clase : 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; } } Paso 4: Utilizar dependencias inyectadas Ahora puedes usar las dependencias inyectadas en tus clases. Por ejemplo, en una : 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 // ... } } Conclusión Resumamos este tema: El objetivo principal de DI es aflojar el acoplamiento, facilitando la gestión de las dependencias. Al utilizar DI, puede aumentar la flexibilidad del código y simplificar el proceso de prueba. DI es un tema complejo con diferentes implementaciones según el escenario. DI en diferentes lenguajes tiene particularidades que pueden afectar la forma en que se trabaja con él. Dagger 2 automatiza el proceso de creación y provisión de dependencias, reduciendo el código repetitivo y mejorando la capacidad de mantenimiento. Dagger 2 proporciona seguridad en tiempo de compilación, garantizando que se satisfagan todas las dependencias antes de que se ejecute la aplicación. Al generar código en tiempo de compilación, Dagger 2 evita la sobrecarga de rendimiento asociada con las soluciones basadas en reflexión.