Dagger 2, um ano depois

Rafael Toledo
Android Dev BR
Published in
5 min readJul 19, 2016

Bibliotecas que ajudam a organizar melhor o código que produzimos são sempre bem vindas, e já faz mais de um ano que comecei a estudar o Dagger 2.

Se você nunca mexeu com Dagger 2, talvez valha a pena dar uma olhada em alguns materiais antes de começar a ler este post. Um bom começo é a apresentação que fiz no TDC SP 2015, que aborda o assunto desde o início, trazendo várias referências relevantes no final :)

Cerca de 15 meses após o lançamento de sua primeira versão estável, como a biblioteca evoluiu? O que tem de novo nesse meio tempo até a versão 2.5, lançada mês passado?

@Reusable

Na versão 2.0 do Dagger já tínhamos um escopo padrão, o @Singleton, herdado da especificação JSR 330, para objetos que deveriam possuir apenas uma única instância ao longo de toda a aplicação. Na versão 2.3 do Dagger, temos a introdução da anotação de escopo @Reusable, que nos fornece uma instância ativa de cada vez, podendo ou não ser reutilizada. Porém, ao contrário do @Singleton, não há garantia desse reuso. Esta anotação é interessante, por exemplo, para classes Helper ou Utils, ou qualquer outro tipo de classe stateless.

A utilização do @Reusable é feita no próprio método anotado com @Provides:

@Module
public class DaggerModule {

@Provides
@Reusable
public TextHelper provideTextHelper(Context context) {
return new TextHelper(context);
}
}

@Binds

Pra quem foi early adopter do Dagger 2 lembra que, quando queríamos trabalhar com interfaces e, ao mesmo tempo, separar a complexidade de criação de alguns objetos (interface e sua classe concreta, por exemplo), nossos módulos não ficavam exatamente elegantes. Até então, tínhamos que fazer algumas coisas como:

@Module
public class BluetoothModule {

@Provides
public BluetoothImpl provideBluetoothImpl() {
return new BluetoothImpl();
}

@Provides
public Bluetooth provideBluetooth(BluetoothImpl impl) {
return impl;
}
}

A partir da versão 2.4, temos a anotação @Binds, que permite que tenhamos módulos abstratos, onde fazemos apenas o bind da implementação e da abstração. Assim, podemos separar melhor a implementação dos módulos, além de possibilitar que o próprio Dagger otimize esse processo, conforme sugere a documentação.

@Module
public class ConcreteModule {

@Provides
public BluetoothImpl provideConcreteImpl() {
return new BluetoothImpl();
}
}
@Module
public abstract class AbstractModule {

@Binds
public abstract Bluetooth bindBluetooth(BluetoothImpl impl);
}

Multibinding

Apesar de existir de forma mais primitiva na versão 2.0, a possibilidade de fazer multibindings e devolver objetos na forma de coleções (sejam elas em Map ou Set) evoluiu muito nas releases seguintes. A própria documentação ilustra diversos cenários e usos para essa funcionalidade.

Para realizar multibindings com Sets, basta declarar anotar os métodos @Provides com @IntoSet (caso o método esteja adicionando apenas um valor a coleção), ou @ElementsIntoSet (caso o método esteja adicionando mais de um valor a coleção).

@Module
public class DaggerModule {

@Provides
@IntoSet
public String provideValueA() {
return "ABC";
}

@Provides
@IntoSet
public String provideValueB() {
return "DEF";
}


@Provides
@ElementsIntoSet
public Set<String> provideMultipleItems() {
return new HashSet<>(Arrays.asList("GHI", "JKL"));
}
}

A implementação de uma Activity ficaria assim:

public class MainActivity extends AppCompatActivity {

@Inject
Set<String> values;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerApp app = (DaggerApp) getApplication();
app.getComponent().inject(this);

// ABC, DEF, GHI e JKL estão presentes no Set
for (String v : values) {
Log.i("SET", v);
}
}
}

Para Maps, o método também deve ser anotado com @IntKey, @StringKey ou @ClassKey, para que a dependência seja inserida corretamente e possa ser acessada pelo objeto injetado.

@Module
public class DaggerModule {

@Provides
@IntoMap
@IntKey(1)

public String provideValue() {
return "ABC";
}
}

E a classe a ser injetada:

public class MainActivity extends AppCompatActivity {

@Inject
Map<Integer, String> values;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerApp app = (DaggerApp) getApplication();
app.getComponent().inject(this);

for (Integer key : values.keySet()) {
Log.i("MAP", values.get(key));
}
}
}

Caso determinado multibinding possa ser uma coleção vazia, é necessário criar um módulo abstrato e criar um método declarando tal coleção, anotando-o com @Multibinds:

@Module
public abstract class AbstractModule {

@Multibinds
public abstract Set<String> providePossiblyEmptySet();
}

Um artigo bem interessante sobre multibindings, do Miroslaw Stanek, onde ele utiliza Dagger, multibindings e Auto Factory para criar uma prova de conceito e injetar diferentes ViewHolders. Vale a leitura!

Producers

Também presente na versão 2.0, mas tendo evoluído e sido otimizado consideravelmente desde a primeira versão, os Producers são uma forma de injeção de dependências assíncrona.

Porém, nesse caso, temos as desvantagens de adicionar a dependência do Guava (o que pode aumentar consideravelmente a quantidade de métodos no APK, caso o Proguard não esteja sendo utilizado) e a verbosidade da solução, que não se baseia na especificação JSR 330.

Para o uso dos Producers, primeiramente temos que adicionar a dependência específica no nosso arquivo build.gradle:

dependencies {
...

compile 'com.google.dagger:dagger:2.5'
compile 'com.google.dagger:dagger-producers:2.5'
apt 'com.google.dagger:dagger-compiler:2.5'
}

Em seguida precisamos criar um módulo que indica como as dependências assíncronas serão entregues, retornando um objeto Executor:

@Module
public class ExecutorModule {

@Provides
@Production
public Executor executor() {
return Executors.newCachedThreadPool();
}
}

As nossas dependências (a serem entregues de forma assíncronas) devem ser declaradas em um @ProducerModule, muito similar aos módulos já utilizados. Já os métodos, devem ser anotados com @Produces — por padrão, todas as dependências são singleton.

@ProducerModule
public class MyProducerModule {

@Produces
public LayoutInflater produceLayoutInflater(Context context) {
return LayoutInflater.from(context);
}
}

Já o componente precisa ser um @ProductionComponent, incluindo o módulo de execução e os Producer Modules. As dependências que serão injetadas são declaradas em métodos retornando um ListenableFuture, assim como no exemplo:

@ProductionComponent(
modules = {MyProducerModule.class, ExecutorModule.class},
dependencies = DaggerComponent.class
)
public interface MyProducerComponent {
ListenableFuture<LayoutInflater> layoutInflater();
}

Os Production Components podem depender de componentes e módulos convencionais.

Por fim, uma das formas de se obter a dependência é através do método Futures.addCallback() do Guava:

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerApp app = (DaggerApp) getApplication();

Futures.addCallback(app.getProducerComponent()
.layoutInflater(),
new FutureCallback<LayoutInflater>() {
@Override
public void onSuccess(LayoutInflater result) {
// result is not null! :)
}

@Override
public void onFailure(Throwable t) {
Log.e("Producers", "Failed to get dependency");
}
});
}
}

Novamente, o artigo do Stanek ilustra o uso para os Producers mais a fundo, indicados para dependências pesadas e custosas — o que, teoricamente, deve ser evitado em um aplicativo Android :)

Conclusão

Posso dizer que a evolução do Dagger tem acontecido de forma bastante satisfatória, principalmente com um ciclo de releases mais constantes desde o início deste ano. Além disso, se antes havia algum receio do uso da biblioteca, posso dizer que alguns projetos em produção com a biblioteca já rodam tranquilamente há meses sem quaisquer problemas relacionados ao uso da biblioteca.

Então, façam bom uso e viva o open source! :)

--

--