«

»

may 18 2015

‘Avengers’ vs Dagger2, RxJava & Retrofit visto desde ‘Clean’

Últimamente, se está hablando mucho de testing y arquitectura de software en la comunidad Android, como se dijo en la última Droidcon Spain, nos estamos enfocando cada día más en como hacer aplicaciones robustas en vez de funcionalidades concretas. Eso denota que el framework y la comunidad Android actual está llegando a cierto nivel de madurez.

Hoy en día si no te suenan las palabras Dagger2, RxJava o Retrofit y eres desarrollador Android, amigo, te estás despistando, ésta serie se enfoca en dar unas ideas básicas de como usar éstos frameworks junto a una ‘arquitectura clean’ que dará robustez y escalabilidad a vuestros proyectos.

Esta primera entrega está dedicada al framework de inyección de dependencias, Dagger 2, el siguiente se centrará en RxJava.

Cómo siempre, todo el código está publicado en GitHub, por favor, todas las recomendaciones, problemas y comentarios son bien recibidos :)

avengers_list

Inyectores de dependencias

Dagger 2 se basa en el patrón de la inyección de dependencias, una inyección de dependencias no deja de ser algo tan simple como lo siguiente:

// Thor is awesome. He has a hammer!
public class Thor extends Avenger {
    private final AvengerWeapon myAmazingHammer;

    public Thor (AvengerWeapon anAmazingHammer) {
        myAmazingHammer = anAmazingHammer;
    }

    public void doAmazingThorWork () {
        myAmazingHammer.hitSomeone();
    }
}

Thor necesita un AvengerWeapon para poder funcionar correctamente, la idea básica es que Thor obtendría menos beneficios creando su propia AvengerWeapon que pasándosela por su constructor. Si Thor se creara su propio martillo estaríamos aumentando el acoplamiento, eso es algo que siempre hay que tratar de minimizar.

La idea es que una clase no tenga que crear sus propios objetos, que sean provistos en el momento de su creación, el primer pensamiento puede ser pasar estos objetos o dependencias por el constructor de una clase, desgraciadamente, en Android, por como está diseñado el framework, no siempre es fácil acceder a los constructores, Activity o Fragment son algunos ejemplos.

Ahí es donde inyectores de dependencias como Dagger 2, Dagger o Guice pueden aportar beneficios.

Usando un inyector como Dagger 2 transformaríamos el código anterior en algo parecido a esto:

// Thor is awesome. He has a hammer!
public class Thor extends Avenger {
    @Inject AvengerWeapon myAmazingHammer;

    public void doAmazingThorWork () {
        myAmazingHammer.hitSomeone();
    }
}

En este caso no estamos accediendo al constructor de Thor directamente, el inyector es quien se encarga a base a unas directivas de construir el martillo de Thor.

public class ThorHammer extends AvengerWeapon () {
    @Inject public AvengerWeapon() {
        initGodHammer();
    }
}

La anotación @Inject indica a Dagger 2 qué es ese constructor el que tiene que utilizar para por defecto construir el martillo de Thor.

Dagger 2

Dagger 2, promovido e implementado por Google es un fork de Dagger desarrollado por Square.

Para usar Dagger 2 en vuestros proyectos en primer lugar es necesario configurar el procesador de anotaciones, el plugin de Gradle, android-apt,  juega ese papel, permitiendo utilizar el procesador de anotaciones sin insertarlo en el .apk final, y configurando el código fuente generado por éste procesador.

build.gradle (En la raíz del proyecto)

dependencies {
    ...
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}

build.gradle (En el módulo android)

apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    ...
    apt 'com.google.dagger:dagger-compiler:2.0'
}

Componentes, módulos y Vengadores

Los módulos son quienes proveen las dependencias, y los componentes quienes las inyectan.

Veamos un ejemplo:

@Module
public class AppModule {
    private final AvengersApplication mAvengersApplication;

    public AppModule(AvengersApplication avengersApplication) {
        this.mAvengersApplication = avengersApplication;
    }

    @Provides @Singleton
    AvengersApplication provideAvengersAppContext () {
        return mAvengersApplication;
    }

    @Provides @Singleton
    Repository provideDataRepository (RestRepository restRepository) {
        return restRepository;
    }
}

Aquí tenemos el módulo principal, nos interesa que sus dependencias sobrevivan durante toda la vida de la aplicación, un contexto general y un repositorio para obtener datos.

Simple, ¿verdad?
Con la anotación @Provides le decimos a Dagger 2 como ha de construirse esa dependencia en caso sea requerida.

En caso no indicáramos un proveedor para determinada dependencia, Dagger 2, irá a buscarla al constructor de la clase que tenga la anotación @Inject.

Los módulos son utilizados por los componentes para inyectar las dependencias, veamos el componente de este módulo.

@Singleton @Component(modules = AppModule.class)
public interface AppComponent {
    AvengersApplication app();
    Repository dataRepository();
}

Este módulo de por sí, no es invocado ni por actividades ni fragments para inyectar las dependencias, en este caso no es necesario, es invocado por módulos más específicos dependientes de éste que si lo harán.

Atención a los dos métodos de este componente:

AvengersApplication app();
Repository dataRepository();

Es obligación exponer las dependencias al grafo, ¿Qué quiere decir esto?, es importante, las dependencias provistas por este componente han de ser visibles para otros componentes dependientes de este.

Si no fueran expuestas y un componente más especifico quisiera resolver éstas dependencias no sabría como hacerlo, no tiene de donde sacarlas.

Veamos nuestro arbol de dependencias:

Dagger graph

Vamos con otro ejemplo:

@Module
public class AvengersModule {

    @Provides @Activity List<Character> provideAvengers() {

        List<Character> avengers = new ArrayList<>(6);

        avengers.add(new Character(
            "Iron Man", R.drawable.thumb_iron_man, 1009368));

        avengers.add(new Character(
            "Thor", R.drawable.thumb_thor, 1009664));

        avengers.add(new Character(
            "Captain America", R.drawable.thumb_cap,1009220));

        avengers.add(new Character(
            "Black Widow", R.drawable.thumb_nat, 1009189));

        avengers.add(new Character(
            "Hawkeye", R.drawable.thumb_hawkeye, 1009338));

        avengers.add(new Character(
            "Hulk", R.drawable.thumb_hulk, 1009351));

        return avengers;
    }
}

Este módulo sera usado para inyectar dependencias en una actividad especifica, en concreto será quien se encargue de pintar la lista de Avengers:

@Activity
@Component(
dependencies = AppComponent.class,
    modules = {
        AvengersModule.class,
        ActivityModule.class
    })
public interface AvengersComponent extends ActivityComponent {
    void inject (AvengersListActivity activity);
    List<Character> avengers();
}

De nuevo exponemos nuestra dependencia List<Character> a otros componentes, y en este caso hay un nuevo método: void inject (AvengersListActivity activity), otro importante, en el momento que éste método sea invocado, las dependencias estarán disponibles para ser consumidas. Estas se inyectarán en AvengerListActivity.

Importantísimo darse cuenta que quien llame en éste caso al método void inject(AvengerListActivity act) ha de ser exactamente AvengerListActivity, de otra forma no se resolverán las dependencias.

También hay que tener claro que este componente necesita al componente AppComponent ya que si le pedimos las dependencias a este componente y no dependiera de AppComponent, las dependencias como el contexto de la aplicación y el repositorio no serían resueltas.

Todo mezcladito

Nuestra clase AvengersApplication será la encargada en proveer el componente de la aplicación a los componentes que lo requieran, nótese que esta no ordena inyectar ninguna dependencia, únicamente aporta el componente.

Otra nota que me hizo romper la cabeza es que Dagger 2 genera los elementos necesarios en tiempo de compilación, esto es, si vosotros no construís vuestro proyecto, no vais a encontrar la clase DaggerAppComponent por ejemplo hasta que hagáis un ‘build‘.

Dagger 2 genera dicha clase a partir de vuestro componente con el siguiente formato: Dagger`$$`{YourComponent}

Tener en cuenta también, que Dagger 2 probablemente no genere nada si tenéis algún pequeño error en vuestro código.

AvengersApplication.java

public class AvengersApplication extends Application {
    private AppComponent mAppComponent;

    @Override
    public void onCreate() {

        super.onCreate();
        initializeInjector();
    }

    private void initializeInjector() {
        mAppComponent = DaggerAppComponent.builder()
            .appModule(new AppModule(this))
            .build();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }
}

AvengersListActivity.java

public class AvengersListActivity extends Activity
implements AvengersView {
    @InjectView(R.id.activity_avengers_recycler)
    RecyclerView mAvengersRecycler;

    @InjectView(R.id.activity_avengers_toolbar)
    Toolbar mAvengersToolbar;

    @Inject
    AvengersListPresenter mAvengersListPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_avengers_list);
        ButterKnife.inject(this);

        initializeToolbar();
        initializeRecyclerView();
        initializeDependencyInjector();
        initializePresenter();
    }

    private void initializeDependencyInjector() {

        AvengersApplication avengersApplication =
            (AvengersApplication) getApplication();

        DaggerAvengersComponent.builder()
            .avengersModule(new AvengersModule())
            .activityModule(new ActivityModule(this))
            .appComponent(avengersApplication.getAppComponent())
            .build().inject(this);
    }
}

En el momento en que initializeDependencyIjector() llega a .inject(this) Dagger 2 comienza a trabajar y provee las dependencias necesarias, recordad que Dagger 2 es estricto a la hora de hacer la inyección, quiero decir, en el componente tiene que ser exactamente la misma clase quien llame al método inject() del componente:

AvengersComponent.java

public interface AvengersComponent extends ActivityComponent {
    void inject (AvengersListActivity activity);
    List<Character> avengers();
}

De otro modo las dependencias no serán resueltas. En este caso, cuando se inicializa el presenter los vengadores son provistos por Dagger 2:

public class AvengersListPresenter implements Presenter, RecyclerClickListener {

    private final List<Character> mAvengersList;
    private final Context mContext;
    private AvengersView mAvengersView;
    private Intent mIntent;

    @Inject public AvengersListPresenter (List<Character> avengers, Context context) {

        mAvengersList = avengers;
        mContext = context;
    }

    // Blah blah
}

Dagger 2 resolverá al presenter porque tiene la anotación @Inject en su constructor, y el argumento que recibe el constructor Dagger 2 sabe resolverlo ya que hemos indicado un @Provides en el módulo quien se encargará de ello. Chulo, ¿verdad?

La potencia de un buen uso de un inyector como Dagger 2 es indiscutible, imaginad que las dependencias se rigieran por un determinado Api Level del framework de android, las posibilidades son infinitas.

Recursos

- Chiu-Ki Chan - Dagger 2 + Espresso + Mockito

- Fernando Cejas - Tasting Dagger 2 on Android

- Google Developers - Dagger 2, A new type of dependency injection

- Mike Gouline - Dagger 2, Even sharper, less square

Acerca del autor

Saúl Molinero Malvido

Android enthusiast. GDG Vigo & GDG Santiago, en twitter @_saulmm