Usando Dagger2. Conceptos básicos

Dagger es una librería, actualmente de Google, para inyección de dependencia (DI). La DI es una solución que tienen los desarrolladores para modularizar la creación de objetos y encapsular sus instancias. 

A pesar de que la DI tiene una integración compleja, su implementación es prácticamente obligatoria en cualquier proyecto actual, ya que su potencial y posibilidad de personalización para la creación de instancias con Dagger son muy potentes.

En este post vamos a explicar los conceptos básicos de Dagger desde que se simplificó su uso a partir de la versión 2.10. Para que te resulte de completa utilidad, vamos a mostrarte el código tanto para Java como para Kotlin.

 

Módulos

Módulos y componentes son las clases principales de Dagger. En los módulos es en donde se declaran las instancias de las clases que nosotros vamos a usar a lo largo de nuestros proyectos. Para ello tenemos dos anotaciones principales con las cual acompañar las instancias: @Provider y @Binds.

Las diferencias entre estas anotaciones se puede resumir en dos:

  1. @Provider nos permite retornar la instancia de un objeto en un bloque de código y controlar cómo se instancia, mientras que @Binds sólo devuelve la instancia de un objeto de manera simple y sin que podamos controlar cómo se instancia.
  2. @Binds autogenera menos código para instanciar los objetos y lo hace en menos tiempo. Por tanto, siempre que sea posible, debemos usar @Binds (normalmente cuando queremos devolver clases abstractas o interfaces).

Para declarar una clase como módulo lo primero es usar @Module encima de la clase. También vamos a acompañar las instancias con @Singleton para indicar que son de instancia únicas.

Java

@Module
public class BaseDIModule {

   @Provides
   @Singleton
   Context provideContext(Application application) {
       return application;
   }
}
@Module
public abstract class DataModule {

   @Binds
   @Singleton
   abstract RestApi provideApiRest(RestApiImpl apiRest);

 

Kotlin

@Module
public abstract class DataModule {

   @Binds
   @Singleton
   abstract RestApi provideApiRest(RestApiImpl apiRest);
@Module
abstract class DataModule {

   @Binds
   @Singleton
   abstract fun provideApiRest(apiRest:RestApiImpl):RestApi
}

 

Nota importante: usamos @Binds en clases abstractas y los @Provide en clases normales.

 

Componentes

Los componentes son clases en las cuales se declaran dónde se van a inyectar los módulos. Los componentes están compuestos de dos partes: las clases donde añadiremos nuestros activity/fragment (las cuales llamaremos Builder) y una interfaz que será la que usará Dagger para crear todo lo que hemos hecho en los módulos (qué será el componente).

Los builders irán anotados con @Module y el componente (desde la versión 2.10 de Dagger ya sólo se suele hablar de un único componente) con @Component y dentro tendrá un array con todos los @Module que hayamos creado.

Java

@Module
public abstract class DaggerBuilder {

   @ContributesAndroidInjector
   abstract BaseActivity bindBaseActivity();

   @ContributesAndroidInjector
   abstract BaseFragment bindBaseFragment();

}
@Component(modules = {
       AndroidInjectionModule.class,
       BaseDIModule.class,
       DataModule.class,
       DaggerBuilder.class})
public interface AppComponent{

   @Component.Builder
   interface Builder {
       @BindsInstance
       Builder application(Application application);
       AppComponent build();
   }

   void inject(AndroidApplication app);
}

 

Kotlin

@Module
abstract class DaggerBuilder {

   @ContributesAndroidInjector
   abstract fun bindBaseActivity(): BaseActivity

   @ContributesAndroidInjector
   abstract fun bindBaseFragment(): BaseFragment
}
@Component(modules= [
    BaseDIModule::class,
    DataModule::class,
    DaggerBuilder::class,
    AndroidInjectionModule::class])
interface AppComponent {

   @Component.Builder
   interface Builder
   {
       @BindsInstance
       fun application(application:Application): Builder
       fun build(): AppComponent
   }

   fun inject (app:AndroidApplication)
}

 

 

Creación de inyectores

Hasta ahora tenemos creado módulos y componentes. Ahora tenemos que implementar el código que va a inyectar todo lo que hemos hecho.

Para ello vamos a usar los injectors, una solución de Dagger para inyectar mas fácilmente en Activities y Fragments (desde Dagger 2.10).

El injector de las Activity tiene que ir en nuestra clase Application, mientras que el injector de los Fragment debe ir en nuestras clases Activity.

Por último, llamaremos a los Injectors usando AndroidInjection.inject(this). Es importante que se le llame antes de llamar a cualquier otro método.

Java

public class AndroidApplication extends Application implements HasActivityInjector {

 @Inject
 DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;

 @Override public void onCreate() {
   super.onCreate();
   this.initializeInjector();
 }

 private void initializeInjector() {
   DaggerAppComponent.builder() //Esta clase solo se creara despues de compilar
           .application(this)
           .build()
           .inject(this);
 }

 @Override
 public DispatchingAndroidInjector<Activity> activityInjector() {
   return activityDispatchingAndroidInjector;
 }

}
public abstract class BaseActivity extends AppCompatActivity implements HasSupportFragmentInjector {

 @Inject
 DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;

 @Override
 public AndroidInjector<Fragment> supportFragmentInjector() {
   return fragmentDispatchingAndroidInjector;
 }

 @Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   AndroidInjection.inject(this); //Llama al Injector de Application
 }
}
public abstract class BaseFragment extends Fragment {

 @Override
 public void onAttach(Context context) {
   AndroidSupportInjection.inject( this); // Llama al Injector de Activity
   super.onAttach(context);
 }
}

 

Kotlin

class AndroidApplication : Application(), HasActivityInjector {

   @Inject
   lateinit var activityInjector : DispatchingAndroidInjector<Activity>

   override fun onCreate() {
       this.injectMembers()
       super.onCreate()
   }

   private fun injectMembers() {
       DaggerAppComponent //Esta clase solo se creara despues de compilar
           .builder()
           .application(this)
           .build().inject(this)
   }

   override fun activityInjector(): AndroidInjector<Activity> = activityInjector

}
abstract class BaseActivity : AppCompatActivity(),HasSupportFragmentInjector{

   @Inject
   lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>

   override fun supportFragmentInjector(): AndroidInjector<Fragment> = dispatchingAndroidInjector

   override fun onCreate(savedInstanceState: Bundle?) {
       AndroidInjection.inject(this) //Llama al Injector de Application
       super.onCreate(savedInstanceState)
   }
}
abstract class BaseFragment: Fragment() {

   override fun onAttach(context: Context) {
       AndroidSupportInjection.inject(this) // Llama al Injector de Activity
       super.onAttach(context)
   }
}

 

 

Llamadas a Dagger

Una vez que hayamos hecho todo esto ya podemos llamar a las instancias de Dagger usando la anotación @Inject.

Java

@Inject
public Context context;
@Inject
public RestApi api;

 

Kotlin

@Inject
lateinit var context: Context
@Inject
lateinit var api: restApi

 

Autoinyecciones

Las autoinyecciones en Dagger permiten encadenar varias clases con anotaciones @Inject.

Para que un @Inject funcione en cualquier clase, el único requisito es que esa clase haya sido inyectada. Por tanto, si en algún momento instanciamos una clase de manera normal (por ejemplo, en el caso de java usando new), los @Inject que tenga en su interior no funcionarán.

 

 

Excepciones

Ahora toca hablar sobre lo malo de Dagger, aquellos componentes que no se instancian de manera normal, como el caso del ViewModel (ViewModelProviders.of(getActivity()).get(ViewModel.class)).

Para que la DI funcione a través del ViewModel nos veremos obligados a usar una clase adicional. Esta clase es el Factory del ViewModel que será inyectado. No nos tenemos que preocupar mucho por esta clase especial, sólo hay que copiar y pegar, pero es necesaria para poder tener Dagger en los ViewModels y será la clase que llevará el @Inject.

Java

import dagger.Lazy; //IMPORTATE ESTA LIBRERIA

@Singleton
public class ViewModelFactory<VM extends ViewModel > implements ViewModelProvider.Factory {
   private Lazy<VM> viewmodel;

   @Inject
   public GoogleViewModelFactory(Lazy<VM> viewmodel) {
       this.viewmodel = viewmodel;
   }

   @NonNull
   @Override
   public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
       return (T) viewmodel.get();
   }
}

 

Kotlin

import dagger.Lazy //IMPORTANTE ESTA LIBRERIA

@Singleton
class ViewModelFactory<VM : ViewModel> @Inject constructor(private val viewModel: Lazy<VM>
) : ViewModelProvider.Factory {

   override fun <T : ViewModel> create(modelClass: Class<T>): T {
       return viewModel.get() as T
   }
}

 

Una vez tenemos esta clase en nuestros proyecto sólo nos queda usar el @Inject y usar el ViewModelProvider para obtener el ViewModel. No hay que usar módulos, ni indicar nada en los componentes.

Java

@Inject
ViewModelFactory<MyViewModel> viewModelFactory;

MyViewModel viewModel = ViewModelProviders.of(getActivity(),viewModelFactory).get(MyViewModel.class);

 

Kotlin

@Inject
lateinit var viewModelFactory: ViewModelFactory<MyViewModel>
MyViewModel viewModel = ViewModelProviders.of(activity!!,viewModelFactory).get(MyViewModel::class.java)

 

 

Conclusiones

Desde la actualización 2.10, Dagger ha simplificado su uso, aunque aún le queden cosas por mejorar que son fundamentales (como la inyección en los ViewModel).

Gracias a esta simplificación se reduce el código que tendremos de Dagger a lo largo de nuestro proyecto, por lo que la librería se vuelve más estable y sencilla de usar.

Aunque actualmente haya otros DI que están ganando popularidad, como es el caso Koin, lo más probable es que en la mayoría de proyectos tengan Dagger, tanto por el soporte que le da Google y su historia. 


 

Posts relacionados

UIColletionViewLayout - SDOS
Cómo implementar un UICollectionViewLayout...
ComunidadDrupal_destacada
Cómo contribuir a la comunidad un módulo en Drupal...
Comentarios
¿Qué opinas? Escríbenos. Nos encantará leerte :)