paint-brush
Inyección de dependencia con Dagger 2: qué es, conceptos clave y máspor@dilip2882
412 lecturas
412 lecturas

Inyección de dependencia con Dagger 2: qué es, conceptos clave y más

por Dilip Patel21m2024/08/28
Read on Terminal Reader

Demasiado Largo; Para Leer

La inyección de dependencias (DI) es un patrón de diseño utilizado para implementar la inversión de control (IoC). 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 administrar.
featured image - Inyección de dependencia con Dagger 2: qué es, conceptos clave y más
Dilip Patel HackerNoon profile picture
0-item


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 Library que requiere una clase Book . Estas clases necesarias se conocen como dependencias. La clase Library depende de tener una instancia de la clase Book para funcionar.

hiperhabilidad.org

Hay tres formas principales para que una clase obtenga los objetos que necesita:

  1. Autoconstrucción : la clase crea e inicializa sus propias dependencias. Por ejemplo, la clase Library crearía e inicializaría su propia instancia de la clase Book .
  2. Recuperación externa : la clase recupera dependencias de una fuente externa. Algunas API de Android, como los captadores Context y getSystemService() , funcionan de esta manera.
  3. Inyección de dependencias : 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 Library recibiría una instancia Book como parámetro.

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 Library que crea su propia dependencia Book podría verse así:

 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 Library construye su propio Book . Esto puede ser problemático porque:

  • Acoplamiento estrecho : Library y Book están acoplados estrechamente. Una instancia de Library utiliza un tipo de Book , lo que dificulta el uso de subclases o implementaciones alternativas.
  • Dificultades de prueba : la dependencia estricta de Book hace que las pruebas sean más desafiantes. Library usa una instancia real de Book , lo que evita el uso de dobles de prueba para modificar Book para diferentes casos de prueba.

Ejemplo con inyección de dependencia

Con DI, en lugar de que cada instancia de Library construya su propio objeto Book , recibe un objeto Book como parámetro en su 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(); }

La función principal utiliza Library . Dado que Library depende de Book , la aplicación crea una instancia de Book y luego la utiliza para construir una instancia de Library . Los beneficios de este enfoque basado en DI son:

  • Reutilización de Library : puedes pasar diferentes implementaciones de Book a Library . Por ejemplo, puedes definir una nueva subclase de Book llamada EBook que quieres que Library use. Con DI, simplemente pasas una instancia de EBook a Library y funciona sin más cambios.
  • Prueba fácil de Library : puedes pasar pruebas dobles para probar diferentes escenarios.

Otro ejemplo de DI

Considere un escenario en el que una clase NotificationService depende de una clase Notification . Sin DI, NotificationService crea directamente una instancia de Notification , lo que dificulta el uso de diferentes tipos de notificaciones o la prueba del servicio con varias implementaciones de notificaciones.

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, NotificationService depende de la interfaz Notification en lugar de una clase específica. Esto permite que se utilicen de forma intercambiable distintas implementaciones de Notification . Puede configurar la implementación que desea utilizar a través del método 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:

  1. Inyección de método (interfaz) : 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.
  2. Inyección de constructor : las dependencias se pasan a la clase a través de su 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. Inyección de campo (o inyección de setter) : 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.

 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. Inyección de métodos : las dependencias se proporcionan a través de métodos, a menudo utilizando la anotación @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:

  • Código repetitivo : 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.
  • Gestión de dependencias complejas : 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.

Las bibliotecas pueden automatizar este proceso creando y proporcionando dependencias. Estas bibliotecas se dividen en dos categorías:

  1. Soluciones basadas en reflexión : conectan dependencias en tiempo de ejecución.
  2. Soluciones estáticas : generan código para conectar dependencias en tiempo de compilación.

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:

  1. Toothpick : 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.

Soluciones estáticas

Estos marcos generan código para conectar dependencias en tiempo de compilación:

  1. Hilt : Hilt, desarrollado sobre Dagger, ofrece una forma estándar de incorporar la inyección de dependencia de Dagger en una aplicación Android. Simplifica la configuración y el uso de Dagger al proporcionar componentes y ámbitos predefinidos .
  2. Koin : un marco de DI ligero y simple para Kotlin. Koin utiliza un DSL para definir dependencias y es fácil de configurar y usar.
  3. Kodein : un marco de DI basado en Kotlin que es fácil de usar y comprender. Proporciona una API simple y flexible para administrar dependencias.

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:

  • @Module : se utiliza para definir clases que proporcionan dependencias. Por ejemplo, un módulo puede proporcionar un ApiClient para Retrofit.
  • @Provides : anota métodos en un módulo para especificar cómo crear y devolver dependencias.
  • @Inject : se utiliza para solicitar dependencias. Se puede aplicar a campos, constructores y métodos.
  • @Component : una interfaz que une @Module y @Inject . Contiene todos los módulos y proporciona el generador para la aplicación.
  • @Singleton : garantiza que se cree una única instancia de una dependencia.
  • @Binds : se utiliza en clases abstractas para proporcionar dependencias, similar a @Provides pero más conciso.

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 @Component , se definen métodos que devuelven instancias de las clases que se necesitan (por ejemplo, BookRepository ). La anotación @Component 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.


Ejemplo

Consideremos un ejemplo que involucra un LibraryRepository :

  1. Anotar el constructor : agregue una anotación @Inject al constructor LibraryRepository para que Dagger sepa cómo crear una instancia de 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. Anotar dependencias : de manera similar, anote los constructores de las dependencias ( LocalLibraryDataSource y RemoteLibraryDataSource ) para que Dagger sepa cómo crearlas.

 public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } }

3. Defina el componente : cree una interfaz anotada con @Component para definir el gráfico de dependencia.

 @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); }

Cuando crea el proyecto, Dagger genera una implementación de la interfaz ApplicationComponent para usted, normalmente llamada 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 @Module que proporcionan dependencias a los componentes. Contienen métodos anotados con @Provides o @Binds 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.

Conceptos clave de los módulos

  1. 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.
  2. 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.
  3. Anotación @Binds: esta anotación se utiliza en clases abstractas para vincular una implementación a una interfaz. Es más concisa que @Provides y se utiliza cuando el módulo es una clase abstracta.

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, NetworkModule es una clase anotada con @Module . Contiene dos métodos anotados con @Provides que crean y devuelven instancias de Retrofit y OkHttpClient .

Usando @Binds

Cuando tienes una interfaz y su implementación, puedes usar @Binds para vincular la implementación a la interfaz. Esto es más conciso que usar @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, ApiModule es una clase abstracta anotada con @Module . El método bindApiService está anotado con @Binds para vincular ApiServiceImpl a 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:

  • NetworkModule : proporciona dependencias relacionadas con la red como Retrofit y OkHttpClient .
  • DatabaseModule : proporciona dependencias relacionadas con la base de datos como RoomDatabase .
  • UIModule : proporciona dependencias relacionadas con la interfaz de usuario, como ViewModel y 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, ApplicationComponent incluye NetworkModule y DatabaseModule para proporcionar dependencias a la aplicación.

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.

  • Ámbito Singleton : garantiza una única instancia de una dependencia durante todo el ciclo de vida de la aplicación.
  • Ámbito de la actividad : garantiza una única instancia de una dependencia dentro del ciclo de vida de una actividad.
  • Alcance del fragmento : garantiza una única instancia de una dependencia dentro del ciclo de vida de un fragmento.

1. Alcance Singleton

Definición : El ámbito @Singleton garantiza que se cree y comparta una única instancia de una dependencia durante todo el ciclo de vida de la aplicación.

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 @Singleton garantiza que las instancias de Retrofit y Database proporcionadas por NetworkModule y DatabaseModule sean singletons y se compartan en toda la aplicación.

2. Ámbito de actividad

Definición : @ActivityScope (un ámbito personalizado) garantiza que se cree y comparta una única instancia de una dependencia dentro del ciclo de vida de una actividad.

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 @ActivityScope garantiza que las dependencias proporcionadas por ActivityModule estén limitadas al ciclo de vida de la actividad.

3. Alcance del fragmento

Definición : @FragmentScope (otro ámbito personalizado) garantiza que se cree y comparta una única instancia de una dependencia dentro del ciclo de vida de un fragmento.

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 @FragmentScope garantiza que las dependencias proporcionadas por FragmentModule tengan como alcance el ciclo de vida del fragmento.

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:

  • Componente de aplicación : proporciona dependencias necesarias en toda la aplicación.
  • Componente de actividad : proporciona dependencias necesarias dentro de una actividad específica.

1. Componente de aplicación

Definición : El componente de aplicación proporciona dependencias que son necesarias en toda la aplicación. Normalmente, se le asigna el alcance @Singleton para garantizar que las dependencias se compartan en toda la aplicación.

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, ApplicationComponent es responsable de proporcionar instancias de Retrofit y Database , que se comparten en toda la aplicación.

2. Componente de actividad

Definición : El componente de actividad proporciona dependencias que son necesarias dentro de una actividad específica. Normalmente, tiene un alcance personalizado, como @ActivityScope , para garantizar que las dependencias se vuelvan a crear cada vez que se vuelve a crear la actividad.

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, ActivityComponent depende de ApplicationComponent y proporciona dependencias específicas de 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:

  • Subcomponentes : un subcomponente es un hijo de otro componente y puede acceder a las dependencias de su padre.
  • Atributo de dependencia : permite que un componente dependa de otro componente sin ser un subcomponente.

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, ActivitySubcomponent es un subcomponente del componente principal y puede acceder a sus dependencias.

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, ActivityComponent depende de ApplicationComponent y puede acceder a sus dependencias.

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.

  • Contexto de aplicación : se utiliza para dependencias que deben durar tanto como la aplicación.
  • Contexto de actividad : se utiliza para dependencias que necesitan durar tanto como una actividad.

1. Contexto de aplicación

Definició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.

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, AppModule proporciona el contexto de la aplicación como una dependencia singleton. El método provideApplicationContext garantiza que el contexto proporcionado esté vinculado al ciclo de vida de la aplicación.

2. Contexto de la actividad

Definición : 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.

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, ActivityModule proporciona el contexto de la actividad como una dependencia con alcance. El método provideActivityContext garantiza que el contexto proporcionado esté vinculado al ciclo de vida de la actividad.

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, MainActivity 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.

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 2.x con la última versión de Dagger 2.

Paso 1: Definir un módulo

Cree un módulo para proporcionar dependencias. Por ejemplo, un NetworkModule para proporcionar una instancia 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.