Nesta apresentação, conheceremos o que o Dagger 2 traz de novo em relação ao seu antecessor, e como ele pode ajudar a deixar a arquitetura da sua aplicação mais organizada de limpa. Além disso, poderemos explorar como ele ainda pode ajudar na testabilidade, fazendo com que o container de dependências possa ser facilmente substituído para injetar mocks em nossos testes. (Título inspirado na música Harder Better Faster Stronger do Daft Punk :D)
4. “Injeção de Dependência (ou Dependency
Injection, em inglês) é um padrão de
desenvolvimento de programas de
computadores utilizado quando é necessário
manter baixo o nível de acoplamento entre
diferentes módulos de um sistema. (...)”
@_rafaeltoledo
5. “Nesta solução as dependências entre os
módulos não são definidas
programaticamente, mas sim pela
configuração de uma infraestrutura de
software (container) que é responsável por
"injetar" em cada componente suas
dependências declaradas. A Injeção de
dependência se relaciona com o padrão
Inversão de controle mas não pode ser
considerada um sinônimo deste.”
Wikipedia, 2015
@_rafaeltoledo
39. Dagger 2
● Fork do Dagger, da Square, feito pela
Google
● Elimina todo o uso de reflections
● Construída sobre as anotações javax.
inject da especificação JSR-330
@_rafaeltoledo
40. Dagger 2
● Validação de todo o grafo de dependências
em tempo de compilação
● Menos flexível, se comparado ao Dagger 1 -
ex.: não possui module overriding
● Proguard configuration-free
@_rafaeltoledo
41. Dagger 2
● API enxuta!
● Código gerado é debugger-friendly
● Muito performático
● google.github.io/dagger
@_rafaeltoledo
42. public @interface Component {
Class<?>[] modules() default {};
Class<?>[] dependencies() default {};
}
public @interface Subcomponent {
Class<?>[] includes() default {};
}
public @interface Module {
Class<?>[] includes() default {};
}
public @interface Provides {
}
public @interface MapKey {
boolean unwrapValue() default true;
}
public interface Lazy<T> {
T get();
}
@_rafaeltoledo
43. // JSR-330
public @interface Inject {
}
public @interface Scope {
}
public @interface Qualifier {
}
@_rafaeltoledo
54. @Inject
● Parte da JSR-330
● Marca quais dependências devem ser
fornecidas pelo Dagger
● Pode aparecer de 3 formas no código
@_rafaeltoledo
55. public class TdcActivityPresenter {
private TdcActivity activity;
private TdcDataStore dataStore;
@Inject
public TdcActivityPresenter(TdcActivity activity,
TdcDataStore dataStore) {
this.activity = activity;
this.dataStore = dataStore;
}
}
1. No Construtor
@_rafaeltoledo
56. 1. No Construtor
● Todas as dependências vem do grafo de
dependências do Dagger
● Anotar uma classe dessa forma faz com que
ela também faça parte do grafo de
dependências (podendo ser injetada em
outras classes)
@_rafaeltoledo
57. 1. No Construtor
● Limitação: não podemos anotar mais que
um construtor com a anotação @Inject
@_rafaeltoledo
58. public class TdcActivity extends AppCompatActivity {
@Inject TdcActivityPresenter presenter;
@Inject SharedPreferences preferences;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
getComponent().inject(this);
}
}
2. Nos Atributos da Classe
@_rafaeltoledo
59. 2. Nos Atributos da Classe
● A injeção nesse caso deve ser manual, caso
contrários os atributos serão todos nulos
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
getComponent().inject(this);
}
● Limitação: os atributos não podem ser
privados!
@_rafaeltoledo
60. public class TdcActivityPresenter {
private TdcActivity activity;
@Inject
public TdcActivityPresenter(TdcActivity activity) {
this.activity = activity;
}
@Inject
public void enableAnalytics(AnalyticsManager analytics) {
analytics.track(this);
}
}
3. Em métodos públicos
@_rafaeltoledo
61. 3. Em métodos públicos
● Utilizado em conjunto com a anotação no
construtor da classe
● Para casos onde o objeto injetado
necessitamos da instância da própria classe
na dependência fornecida
@Inject
public void enableAnalytics(AnalyticsManager analytics) {
analytics.track(this);
}
@_rafaeltoledo
62. 3. Em métodos públicos
● O método anotado é chamado
automaticamente logo após o construtor
@_rafaeltoledo
63. @Module
● Parte da API do Dagger
● Utilizada para identificar classes que
fornecem dependências
@_rafaeltoledo
64. @Module
public class DataModule {
@Provides @Singleton
public OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectionTimeout(10, TimeUnit.SECONDS);
okHttpClient.setReadTimeout(10, TimeUnit.SECONDS);
okHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
return okHttpClient;
}
@Provides @Singleton
public RestAdapter provideRestAdapter(OkHttpClient client) {
return new RestAdapter.Builder()
.setClient(new OkClient(okHttpClient))
.setEndpoint("https://api.github.com")
.build();
}
} @_rafaeltoledo
65. @Module
public class DataModule {
@Provides @Singleton
public OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectionTimeout(10, TimeUnit.SECONDS);
okHttpClient.setReadTimeout(10, TimeUnit.SECONDS);
okHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
return okHttpClient;
}
@Provides @Singleton
public RestAdapter provideRestAdapter(OkHttpClient client) {
return new RestAdapter.Builder()
.setClient(new OkClient(okHttpClient))
.setEndpoint("https://api.github.com")
.build();
}
} @_rafaeltoledo
66. @Provides
● Utilizada nas classes anotadas como
@Module para identificar quais métodos
retornam dependências
@_rafaeltoledo
67. @Module
public class DataModule {
@Provides @Singleton
public OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectionTimeout(10, TimeUnit.SECONDS);
okHttpClient.setReadTimeout(10, TimeUnit.SECONDS);
okHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
return okHttpClient;
}
@Provides @Singleton
public RestAdapter provideRestAdapter(OkHttpClient client) {
return new RestAdapter.Builder()
.setClient(new OkClient(okHttpClient))
.setEndpoint("https://api.github.com")
.build();
}
} @_rafaeltoledo
68. @Module
public class DataModule {
@Provides @Singleton
public OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectionTimeout(10, TimeUnit.SECONDS);
okHttpClient.setReadTimeout(10, TimeUnit.SECONDS);
okHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
return okHttpClient;
}
@Provides @Singleton
public RestAdapter provideRestAdapter(OkHttpClient client) {
return new RestAdapter.Builder()
.setClient(new OkClient(okHttpClient))
.setEndpoint("https://api.github.com")
.build();
}
} @_rafaeltoledo
69. @Component
● É a anotação que liga os @Modules aos
@Injects
● É colocada em uma interface
● Define quais módulos possui, quem será
injetado
@_rafaeltoledo
70. @Component
● Precisa especificar obrigatoriamente seu
escopo (ciclo de vida de suas dependências)
● Pode publicar dependências
● Pode possuir outros componentes
@_rafaeltoledo
94. // Uso
@Inject
Map<String, String> map; // {um=primeiro valor, dois=segundo valor}
Por enquanto, só aceita Map e Set, e valores
do tipo String e Enumeradores
@MapKey
@_rafaeltoledo
96. O que eu ganho de fato com tudo isso?
@_rafaeltoledo
97. Grandes benefícios
● Código mais limpo e organizado
● Melhorias no gerenciamento de objetos
(melhor controle de singletons)
● Código mais fácil de TESTAR
@_rafaeltoledo
98. Dagger 2 e Testes
● Programar orientado a interfaces - mocks
● Sobrescrita de Componentes e Módulos
@_rafaeltoledo
99. Dagger 2 e Testes
@Module
public class TestMainModule {
@Provides // Poderíamos fazer com o Retrofit Mock!
public ApiService provideApiService() {
return new ApiService() {
@Override
public List<Repo> listRepos() {
return Arrays.asList(
new Repo("my-test-repo"),
new Repo("another-test-repo")
);
}
};
}
} @_rafaeltoledo
100. Dagger 2 e Testes
@Component(modules = DebugMainModule.class)
public interface TestMainComponent extends MainComponent {
}
@_rafaeltoledo
101. Dagger 2 e Espresso 2
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
// ...
@Before
public void setUp() {
Instrumentation instrumentation =
InstrumentationRegistry.getInstrumentation();
MainApplication app = (MainApplication)
instrumentation.getTargetContext().getApplicationContext();
app.setComponent(DaggerDebugMainComponent.create());
rule.launchActivity(new Intent());
}
}
@_rafaeltoledo
102. Dagger 2 e Robolectric 3
@RunWith(RobolectricGradleTestRunner.class)
@Config(sdk = 21, constants = BuildConfig.class)
public class MainActivityTest {
// ...
@Before
public void setUp() {
MainComponent component = DaggerDebugMainComponent.create();
MainApplication app =
((MainApplication) RuntimeEnvironment.application)
.setComponent(component);
}
}
@_rafaeltoledo
104. Para saber mais
● Site oficial - google.github.io/dagger
● Dagger 2 - A New Type of Dependency
Injection (Gregory Kick) - vai.la/fdwt
● The Future of Dependency Injection with
Dagger 2 (Jake Wharton) - vai.la/fdwy
● froger_mcs Dev Blog - frogermcs.github.io