Better Faster
Stronger Dagger
Rafael Toledo
@_rafaeltoledo
Android Developer @ Concrete Solutions
www.rafaeltoledo.net
O que é Injeção de
Dependência?
@_rafaeltoledo
“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
“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
?????????
@_rafaeltoledo
Diz respeito a separação de onde os
objetos são criados e onde são utilizados
@_rafaeltoledo
Em vez de criar, você pede
@_rafaeltoledo
class UserController {
void doLogic() {
try {
User user = RetrofitApi.getInstance().getUser(1);
new UserDaoImpl().save(user);
Logger.getForClass(UserController.class).log("Success");
} catch (IOException e) {
Logger.getForClass(UserController.class).logException(e);
}
}
}
@_rafaeltoledo
class UserController {
UserDaoImpl dao;
Logger logger;
UserApi api;
void doLogic() {
try {
api = RetrofitApi.getInstance();
User user = api.getUser(1);
dao = new UserDaoImpl();
dao.save(user);
logger = Logger.getForClass(UserController.class);
logger.log("Success");
} catch (IOException e) {
logger = Logger.getForClass(UserController.class);
logger.logException(e);
}
}
} @_rafaeltoledo
class UserController {
UserDao dao; // Interface!
Logger logger;
Api api;
void doLogic() {
try {
if (api == null) api = RetrofitApi.getInstance();
User user = api.getUser(1);
if (dao == null) dao = new UserDaoImpl();
dao.save(user);
if (logger == null) logger = Logger.getForClass(UserController.class);
logger.log("Success");
} catch (IOException e) {
if (logger == null) logger = Logger.getForClass(UserController.class);
logger.logException(e);
}
}
} @_rafaeltoledo
class UserController {
UserDao dao = new UserDaoImpl();
Logger logger = Logger.getForClass(UserController.class);
Api api = RetrofitApi.getInstance();
void doLogic() {
try {
User user = api.getUser(1);
dao.save(user);
logger.log("Success");
} catch (IOException e) {
logger.logException(e);
}
}
}
@_rafaeltoledo
class UserController {
UserDao dao;
Logger logger;
Api api;
public UserController() {
dao = new UserDaoImpl();
api = RetrofitApi.getInstance();
logger = Logger.getForClass(UserController.class);
}
void doLogic() {
try {
User user = api.getUser(1);
dao.save(user);
logger.log("Success");
} catch (IOException e) { logger.logException(e); }
}
} @_rafaeltoledo
class UserController {
UserDao dao;
Logger logger;
Api api;
public UserController() {
dao = new UserDaoImpl();
api = RetrofitApi.getInstance();
logger = Logger.getForClass(UserController.class);
}
void doLogic() {
try {
User user = api.getUser(1);
dao.save(user);
logger.log("Success");
} catch (IOException e) { logger.logException(e); }
}
} @_rafaeltoledo
class UserController {
UserDao dao;
Logger logger;
Api api;
public UserController() {
dao = new UserDaoImpl();
api = RetrofitApi.getInstance();
logger = Logger.getForClass(UserController.class);
}
void doLogic() {
try {
User user = api.getUser(1);
dao.save(user);
logger.log("Success");
} catch (IOException e) { logger.logException(e); }
}
} @_rafaeltoledo
class UserController {
UserDao dao;
Logger logger;
Api api;
public UserController() {
dao = new UserDaoImpl();
api = RetrofitApi.getInstance();
logger = Logger.getForClass(UserController.class);
}
void doLogic() {
try {
User user = api.getUser(1);
dao.save(user);
logger.log("Success");
} catch (IOException e) { logger.logException(e); }
}
} @_rafaeltoledo
UserDao
UserController
@_rafaeltoledo
UserDao
UserController
SessionManager
@_rafaeltoledo
UserDao
UserController
SessionManager
SignInController
@_rafaeltoledo
UserDao
UserController
SessionManager
SignInController
CookieJob
JobManager
JobController
PermissionChecker
ReminderJob
RoleController
CandyShopper
FruitJuicerJob
UnknownController
@_rafaeltoledo
UserDao
UserController
SessionManager
SignInController
CookieJob
JobManager
JobController
PermissionChecker
ReminderJob
RoleController
CandyShopper
FruitJuicerJob
UnknownController
EM
TODO
LUGAR!
@_rafaeltoledo
class UserDaoImpl implements UserDao {
public UserDaoImpl() {
//...
}
}
@_rafaeltoledo
class UserDaoImpl implements UserDao {
public UserDaoImpl(Context context) {
//...
}
}
@_rafaeltoledo
@_rafaeltoledo
Alteração em todas as classes!
@_rafaeltoledo
Alteração em todas as classes!
Muito retrabalho!
@_rafaeltoledo
Alteração em todas as classes!
Muito retrabalho!
@_rafaeltoledo
Alteração em todas as classes!
Muito retrabalho!
@_rafaeltoledo
class UserController {
UserDao dao;
Logger logger;
Api api;
public UserController() {
dao = new UserDaoImpl();
api = RetrofitApi.getInstance();
logger = Logger.getForClass(UserController.class);
}
void doLogic() {
try {
User user = api.getUser(1);
dao.save(user);
logger.log("Success");
} catch (IOException e) { logger.logException(e); }
}
} @_rafaeltoledo
class UserController {
UserDao dao;
Logger logger;
Api api;
public UserController() {
dao = new UserDaoImpl();
api = RetrofitApi.getInstance();
logger = Logger.getForClass(UserController.class);
}
void doLogic() {
try {
User user = api.getUser(1);
dao.save(user);
logger.log("Success");
} catch (IOException e) { logger.logException(e); }
}
} @_rafaeltoledo
class UserController {
UserDao dao;
Logger logger;
Api api;
public UserController(UserDao dao, Api api, Logger logger) {
this.dao = dao;
this.api = api;
this.logger = logger;
}
void doLogic() {
try {
User user = api.getUser(1);
dao.save(user);
logger.log("Success");
} catch (IOException e) { logger.logException(e); }
}
} @_rafaeltoledo
DI: Separação do uso e criação de objetos
@_rafaeltoledo
DI: Separação do uso e criação de objetos
não há a necessidade de bibliotecas
ou frameworks!
@_rafaeltoledo
Desvantagem: muito boilerplate
@_rafaeltoledo
Spring, Guice, Dagger 1 & cia
#oldbutgold
@_rafaeltoledo
Spring, Guice, Dagger 1 & cia
#oldbutnot
@_rafaeltoledo
Dagger 2
@_rafaeltoledo
Dagger 2?
@_rafaeltoledo
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
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
Dagger 2
● API enxuta!
● Código gerado é debugger-friendly
● Muito performático
● google.github.io/dagger
@_rafaeltoledo
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
// JSR-330
public @interface Inject {
}
public @interface Scope {
}
public @interface Qualifier {
}
@_rafaeltoledo
Como integro no meu projeto?
@_rafaeltoledo
// build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
}
}
allprojects {
repositories {
jcenter()
}
}
@_rafaeltoledo
// build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
}
}
allprojects {
repositories {
jcenter()
}
}
@_rafaeltoledo
// build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
allprojects {
repositories {
jcenter()
}
}
@_rafaeltoledo
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
applicationId 'net.rafaeltoledo.tdc2015'
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName '1.0'
}
}
dependencies {
compile 'com.android.support:appcompat-v7:23.0.1'
}
@_rafaeltoledo
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
applicationId 'net.rafaeltoledo.tdc2015'
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName '1.0'
}
}
dependencies {
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.google.dagger:dagger:2.0.1'
apt 'com.google.dagger:dagger-compiler:2.0.1'
provided 'javax.annotation:javax.annotation-api:1.2'
}
@_rafaeltoledo
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
applicationId 'net.rafaeltoledo.tdc2015'
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName '1.0'
}
}
dependencies {
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.google.dagger:dagger:2.0.1'
apt 'com.google.dagger:dagger-compiler:2.0.1'
provided 'javax.annotation:javax.annotation-api:1.2' // JSR-330
}
@_rafaeltoledo
E pronto!
@_rafaeltoledo
Vamos começar?
@_rafaeltoledo
@Inject
@Component @Module
@Provides
Dagger 2
@_rafaeltoledo
@Inject
● Parte da JSR-330
● Marca quais dependências devem ser
fornecidas pelo Dagger
● Pode aparecer de 3 formas no código
@_rafaeltoledo
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
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
1. No Construtor
● Limitação: não podemos anotar mais que
um construtor com a anotação @Inject
@_rafaeltoledo
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
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
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
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
3. Em métodos públicos
● O método anotado é chamado
automaticamente logo após o construtor
@_rafaeltoledo
@Module
● Parte da API do Dagger
● Utilizada para identificar classes que
fornecem dependências
@_rafaeltoledo
@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
@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
@Provides
● Utilizada nas classes anotadas como
@Module para identificar quais métodos
retornam dependências
@_rafaeltoledo
@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
@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
@Component
● É a anotação que liga os @Modules aos
@Injects
● É colocada em uma interface
● Define quais módulos possui, quem será
injetado
@_rafaeltoledo
@Component
● Precisa especificar obrigatoriamente seu
escopo (ciclo de vida de suas dependências)
● Pode publicar dependências
● Pode possuir outros componentes
@_rafaeltoledo
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@_rafaeltoledo
@Singleton // Escopo
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@_rafaeltoledo
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@_rafaeltoledo
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity); // Caso queira encadear
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@_rafaeltoledo
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager(); // Dependências visíveis
} // para outros componentes*
@_rafaeltoledo
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@_rafaeltoledo
@ActivityScope // Escopo personalizado
@Component(
modules = TdcActivityModule.class,
dependencies = TdcComponent.class
)
public interface TdcActivityComponent {
TdcActivity inject(TdcActivity activity);
TdcActivityPresenter presenter();
}
@_rafaeltoledo
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerMainComponent implements MainComponent {
private Provider<ApiService> provideApiServiceProvider;
private MembersInjector<MainActivity> mainActivityMembersInjector;
private DaggerMainComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static MainComponent create() {
return builder().build();
}
private void initialize(final Builder builder) {
this.provideApiServiceProvider =
ScopedProvider.create(MainModule_ProvideApiServiceFactory.create(builder.mainModule));
this.mainActivityMembersInjector = MainActivity_MembersInjector.create(
(MembersInjector) MembersInjectors.noOp(), provideApiServiceProvider);
}
@Override
public void inject(MainActivity activity) {
mainActivityMembersInjector.injectMembers(activity);
}
}
@_rafaeltoledo
public class TdcApp extends Application {
TdcAppComponent component;
@Override
public void onCreate() {
component = DaggerTdcAppComponent.create();
component = DaggerTdcAppComponent.builder()
.dataModule(new DataModule(this)) // Módulo com construtor
.build(); // parametrizado
}
}
Instanciando um Componente
@_rafaeltoledo
Dagger 2
Escopos dinâmicos
@_rafaeltoledo
Escopo = ciclo de vida do grafo de
dependências (componente)
@_rafaeltoledo
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@_rafaeltoledo
@ActivityScope // Escopo personalizado
@Component(
modules = TdcActivityModule.class,
dependencies = TdcComponent.class
)
public interface TdcActivityComponent {
TdcActivity inject(TdcActivity activity);
TdcActivityPresenter presenter();
}
@_rafaeltoledo
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}
Definição de um escopo
@_rafaeltoledo
Exemplo de uso
Miroslaw Stanek frogermcs.github.io
@_rafaeltoledo
@Singleton
@Component(modules = DataModule.class)
public interface TdcAppComponent {
UserComponent plus(UserModule module);
// não é necessário expor as dependências
}
Implementação
@_rafaeltoledo
@UserScope
@Subcomponent(modules = UserModule.class)
public interface UserComponent {
UserActivityComponent plus(UserActivityModule module);
}
Implementação
@_rafaeltoledo
public class TdcApp extends Application {
TdcAppComponent component;
UserComponent userComponent;
public UserComponent createUserComponent(User user) {
userComponent = component.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
...
}
Implementação
@_rafaeltoledo
Dagger 2
Outras coisas legais!
@_rafaeltoledo
@Inject
Lazy<SharedPreferences> prefs;
void salvar() {
prefs.get().edit().putString("status", "ok!").apply();
}
Dependências “Preguiçosas”
@_rafaeltoledo
// No módulo
@Provides @Singleton
@ApiUrl String provideApiUrl(Context context) {
return context.getString(R.string.api_url);
}
@Provides @Singleton
@AccessToken String provideRestAdapter(SharedPreferences prefs) {
return prefs.getString("token", null);
}
// Na classe a ser injetada
@Inject @AccessToken
String accessToken;
Qualificadores
@_rafaeltoledo
// Declarando sua anotação
@MapKey(unwrapValue = true)
@interface GroupKey {
String value();
}
@MapKey
@_rafaeltoledo
// Fornecendo dependências no módulo
@Provides(type = Type.MAP)
@GroupKey("um")
String provideFirstValue() {
return "primeiro valor";
}
@Provides(type = Type.MAP)
@GroupKey("dois")
String provideSecondValue() {
return "segundo valor";
}
@MapKey
@_rafaeltoledo
// 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
Ok! Tudo muito bonito mas...
@_rafaeltoledo
O que eu ganho de fato com tudo isso?
@_rafaeltoledo
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
Dagger 2 e Testes
● Programar orientado a interfaces - mocks
● Sobrescrita de Componentes e Módulos
@_rafaeltoledo
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
Dagger 2 e Testes
@Component(modules = DebugMainModule.class)
public interface TestMainComponent extends MainComponent {
}
@_rafaeltoledo
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
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
Dagger 2
github.com/rafaeltoledo/dagger2-tdc2015
@_rafaeltoledo
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
OBRIGADO!
rafaeltoledo.net
@_rafaeltoledo
Desenv. Android @ Concrete Solutions
concretesolutions.com.br/carreira

better faster stronger dagger

  • 1.
  • 2.
    Rafael Toledo @_rafaeltoledo Android Developer@ Concrete Solutions www.rafaeltoledo.net
  • 3.
    O que éInjeção de Dependência? @_rafaeltoledo
  • 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 asdependê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
  • 6.
  • 7.
    Diz respeito aseparação de onde os objetos são criados e onde são utilizados @_rafaeltoledo
  • 8.
    Em vez decriar, você pede @_rafaeltoledo
  • 9.
    class UserController { voiddoLogic() { try { User user = RetrofitApi.getInstance().getUser(1); new UserDaoImpl().save(user); Logger.getForClass(UserController.class).log("Success"); } catch (IOException e) { Logger.getForClass(UserController.class).logException(e); } } } @_rafaeltoledo
  • 10.
    class UserController { UserDaoImpldao; Logger logger; UserApi api; void doLogic() { try { api = RetrofitApi.getInstance(); User user = api.getUser(1); dao = new UserDaoImpl(); dao.save(user); logger = Logger.getForClass(UserController.class); logger.log("Success"); } catch (IOException e) { logger = Logger.getForClass(UserController.class); logger.logException(e); } } } @_rafaeltoledo
  • 11.
    class UserController { UserDaodao; // Interface! Logger logger; Api api; void doLogic() { try { if (api == null) api = RetrofitApi.getInstance(); User user = api.getUser(1); if (dao == null) dao = new UserDaoImpl(); dao.save(user); if (logger == null) logger = Logger.getForClass(UserController.class); logger.log("Success"); } catch (IOException e) { if (logger == null) logger = Logger.getForClass(UserController.class); logger.logException(e); } } } @_rafaeltoledo
  • 12.
    class UserController { UserDaodao = new UserDaoImpl(); Logger logger = Logger.getForClass(UserController.class); Api api = RetrofitApi.getInstance(); void doLogic() { try { User user = api.getUser(1); dao.save(user); logger.log("Success"); } catch (IOException e) { logger.logException(e); } } } @_rafaeltoledo
  • 13.
    class UserController { UserDaodao; Logger logger; Api api; public UserController() { dao = new UserDaoImpl(); api = RetrofitApi.getInstance(); logger = Logger.getForClass(UserController.class); } void doLogic() { try { User user = api.getUser(1); dao.save(user); logger.log("Success"); } catch (IOException e) { logger.logException(e); } } } @_rafaeltoledo
  • 14.
    class UserController { UserDaodao; Logger logger; Api api; public UserController() { dao = new UserDaoImpl(); api = RetrofitApi.getInstance(); logger = Logger.getForClass(UserController.class); } void doLogic() { try { User user = api.getUser(1); dao.save(user); logger.log("Success"); } catch (IOException e) { logger.logException(e); } } } @_rafaeltoledo
  • 15.
    class UserController { UserDaodao; Logger logger; Api api; public UserController() { dao = new UserDaoImpl(); api = RetrofitApi.getInstance(); logger = Logger.getForClass(UserController.class); } void doLogic() { try { User user = api.getUser(1); dao.save(user); logger.log("Success"); } catch (IOException e) { logger.logException(e); } } } @_rafaeltoledo
  • 16.
    class UserController { UserDaodao; Logger logger; Api api; public UserController() { dao = new UserDaoImpl(); api = RetrofitApi.getInstance(); logger = Logger.getForClass(UserController.class); } void doLogic() { try { User user = api.getUser(1); dao.save(user); logger.log("Success"); } catch (IOException e) { logger.logException(e); } } } @_rafaeltoledo
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    class UserDaoImpl implementsUserDao { public UserDaoImpl() { //... } } @_rafaeltoledo
  • 23.
    class UserDaoImpl implementsUserDao { public UserDaoImpl(Context context) { //... } } @_rafaeltoledo
  • 24.
  • 25.
    Alteração em todasas classes! @_rafaeltoledo
  • 26.
    Alteração em todasas classes! Muito retrabalho! @_rafaeltoledo
  • 27.
    Alteração em todasas classes! Muito retrabalho! @_rafaeltoledo
  • 28.
    Alteração em todasas classes! Muito retrabalho! @_rafaeltoledo
  • 29.
    class UserController { UserDaodao; Logger logger; Api api; public UserController() { dao = new UserDaoImpl(); api = RetrofitApi.getInstance(); logger = Logger.getForClass(UserController.class); } void doLogic() { try { User user = api.getUser(1); dao.save(user); logger.log("Success"); } catch (IOException e) { logger.logException(e); } } } @_rafaeltoledo
  • 30.
    class UserController { UserDaodao; Logger logger; Api api; public UserController() { dao = new UserDaoImpl(); api = RetrofitApi.getInstance(); logger = Logger.getForClass(UserController.class); } void doLogic() { try { User user = api.getUser(1); dao.save(user); logger.log("Success"); } catch (IOException e) { logger.logException(e); } } } @_rafaeltoledo
  • 31.
    class UserController { UserDaodao; Logger logger; Api api; public UserController(UserDao dao, Api api, Logger logger) { this.dao = dao; this.api = api; this.logger = logger; } void doLogic() { try { User user = api.getUser(1); dao.save(user); logger.log("Success"); } catch (IOException e) { logger.logException(e); } } } @_rafaeltoledo
  • 32.
    DI: Separação douso e criação de objetos @_rafaeltoledo
  • 33.
    DI: Separação douso e criação de objetos não há a necessidade de bibliotecas ou frameworks! @_rafaeltoledo
  • 34.
  • 35.
    Spring, Guice, Dagger1 & cia #oldbutgold @_rafaeltoledo
  • 36.
    Spring, Guice, Dagger1 & cia #oldbutnot @_rafaeltoledo
  • 37.
  • 38.
  • 39.
    Dagger 2 ● Forkdo 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çãode 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 ● APIenxuta! ● 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 @interfaceInject { } public @interface Scope { } public @interface Qualifier { } @_rafaeltoledo
  • 44.
    Como integro nomeu projeto? @_rafaeltoledo
  • 45.
    // build.gradle buildscript { repositories{ jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.1' } } allprojects { repositories { jcenter() } } @_rafaeltoledo
  • 46.
    // build.gradle buildscript { repositories{ jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.1' } } allprojects { repositories { jcenter() } } @_rafaeltoledo
  • 47.
    // build.gradle buildscript { repositories{ jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.1' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } allprojects { repositories { jcenter() } } @_rafaeltoledo
  • 48.
    apply plugin: 'com.android.application' android{ compileSdkVersion 23 buildToolsVersion '23.0.1' defaultConfig { applicationId 'net.rafaeltoledo.tdc2015' minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName '1.0' } } dependencies { compile 'com.android.support:appcompat-v7:23.0.1' } @_rafaeltoledo
  • 49.
    apply plugin: 'com.android.application' applyplugin: 'com.neenbedankt.android-apt' android { compileSdkVersion 23 buildToolsVersion '23.0.1' defaultConfig { applicationId 'net.rafaeltoledo.tdc2015' minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName '1.0' } } dependencies { compile 'com.android.support:appcompat-v7:23.0.1' compile 'com.google.dagger:dagger:2.0.1' apt 'com.google.dagger:dagger-compiler:2.0.1' provided 'javax.annotation:javax.annotation-api:1.2' } @_rafaeltoledo
  • 50.
    apply plugin: 'com.android.application' applyplugin: 'com.neenbedankt.android-apt' android { compileSdkVersion 23 buildToolsVersion '23.0.1' defaultConfig { applicationId 'net.rafaeltoledo.tdc2015' minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName '1.0' } } dependencies { compile 'com.android.support:appcompat-v7:23.0.1' compile 'com.google.dagger:dagger:2.0.1' apt 'com.google.dagger:dagger-compiler:2.0.1' provided 'javax.annotation:javax.annotation-api:1.2' // JSR-330 } @_rafaeltoledo
  • 51.
  • 52.
  • 53.
  • 54.
    @Inject ● Parte daJSR-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 TdcActivityextends 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 Atributosda 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étodospú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étodospúblicos ● O método anotado é chamado automaticamente logo após o construtor @_rafaeltoledo
  • 63.
    @Module ● Parte daAPI 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 nasclasses 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 ● É aanotação que liga os @Modules aos @Injects ● É colocada em uma interface ● Define quais módulos possui, quem será injetado @_rafaeltoledo
  • 70.
    @Component ● Precisa especificarobrigatoriamente seu escopo (ciclo de vida de suas dependências) ● Pode publicar dependências ● Pode possuir outros componentes @_rafaeltoledo
  • 71.
    @Singleton @Component( modules = { DataModule.class, UiModule.class } ) publicinterface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  • 72.
    @Singleton // Escopo @Component( modules= { DataModule.class, UiModule.class } ) public interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  • 73.
    @Singleton @Component( modules = { DataModule.class, UiModule.class } ) publicinterface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  • 74.
    @Singleton @Component( modules = { DataModule.class, UiModule.class } ) publicinterface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); // Caso queira encadear Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  • 75.
    @Singleton @Component( modules = { DataModule.class, UiModule.class } ) publicinterface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); // Dependências visíveis } // para outros componentes* @_rafaeltoledo
  • 76.
    @Singleton @Component( modules = { DataModule.class, UiModule.class } ) publicinterface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  • 77.
    @ActivityScope // Escopopersonalizado @Component( modules = TdcActivityModule.class, dependencies = TdcComponent.class ) public interface TdcActivityComponent { TdcActivity inject(TdcActivity activity); TdcActivityPresenter presenter(); } @_rafaeltoledo
  • 78.
    @Generated("dagger.internal.codegen.ComponentProcessor") public final classDaggerMainComponent implements MainComponent { private Provider<ApiService> provideApiServiceProvider; private MembersInjector<MainActivity> mainActivityMembersInjector; private DaggerMainComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static MainComponent create() { return builder().build(); } private void initialize(final Builder builder) { this.provideApiServiceProvider = ScopedProvider.create(MainModule_ProvideApiServiceFactory.create(builder.mainModule)); this.mainActivityMembersInjector = MainActivity_MembersInjector.create( (MembersInjector) MembersInjectors.noOp(), provideApiServiceProvider); } @Override public void inject(MainActivity activity) { mainActivityMembersInjector.injectMembers(activity); } } @_rafaeltoledo
  • 79.
    public class TdcAppextends Application { TdcAppComponent component; @Override public void onCreate() { component = DaggerTdcAppComponent.create(); component = DaggerTdcAppComponent.builder() .dataModule(new DataModule(this)) // Módulo com construtor .build(); // parametrizado } } Instanciando um Componente @_rafaeltoledo
  • 80.
  • 81.
    Escopo = ciclode vida do grafo de dependências (componente) @_rafaeltoledo
  • 82.
    @Singleton @Component( modules = { DataModule.class, UiModule.class } ) publicinterface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  • 83.
    @ActivityScope // Escopopersonalizado @Component( modules = TdcActivityModule.class, dependencies = TdcComponent.class ) public interface TdcActivityComponent { TdcActivity inject(TdcActivity activity); TdcActivityPresenter presenter(); } @_rafaeltoledo
  • 84.
  • 85.
    Exemplo de uso MiroslawStanek frogermcs.github.io @_rafaeltoledo
  • 86.
    @Singleton @Component(modules = DataModule.class) publicinterface TdcAppComponent { UserComponent plus(UserModule module); // não é necessário expor as dependências } Implementação @_rafaeltoledo
  • 87.
    @UserScope @Subcomponent(modules = UserModule.class) publicinterface UserComponent { UserActivityComponent plus(UserActivityModule module); } Implementação @_rafaeltoledo
  • 88.
    public class TdcAppextends Application { TdcAppComponent component; UserComponent userComponent; public UserComponent createUserComponent(User user) { userComponent = component.plus(new UserModule(user)); return userComponent; } public void releaseUserComponent() { userComponent = null; } ... } Implementação @_rafaeltoledo
  • 89.
    Dagger 2 Outras coisaslegais! @_rafaeltoledo
  • 90.
    @Inject Lazy<SharedPreferences> prefs; void salvar(){ prefs.get().edit().putString("status", "ok!").apply(); } Dependências “Preguiçosas” @_rafaeltoledo
  • 91.
    // No módulo @Provides@Singleton @ApiUrl String provideApiUrl(Context context) { return context.getString(R.string.api_url); } @Provides @Singleton @AccessToken String provideRestAdapter(SharedPreferences prefs) { return prefs.getString("token", null); } // Na classe a ser injetada @Inject @AccessToken String accessToken; Qualificadores @_rafaeltoledo
  • 92.
    // Declarando suaanotação @MapKey(unwrapValue = true) @interface GroupKey { String value(); } @MapKey @_rafaeltoledo
  • 93.
    // Fornecendo dependênciasno módulo @Provides(type = Type.MAP) @GroupKey("um") String provideFirstValue() { return "primeiro valor"; } @Provides(type = Type.MAP) @GroupKey("dois") String provideSecondValue() { return "segundo valor"; } @MapKey @_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
  • 95.
    Ok! Tudo muitobonito mas... @_rafaeltoledo
  • 96.
    O que euganho de fato com tudo isso? @_rafaeltoledo
  • 97.
    Grandes benefícios ● Códigomais limpo e organizado ● Melhorias no gerenciamento de objetos (melhor controle de singletons) ● Código mais fácil de TESTAR @_rafaeltoledo
  • 98.
    Dagger 2 eTestes ● Programar orientado a interfaces - mocks ● Sobrescrita de Componentes e Módulos @_rafaeltoledo
  • 99.
    Dagger 2 eTestes @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 eTestes @Component(modules = DebugMainModule.class) public interface TestMainComponent extends MainComponent { } @_rafaeltoledo
  • 101.
    Dagger 2 eEspresso 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 eRobolectric 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
  • 103.
  • 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
  • 105.
    OBRIGADO! rafaeltoledo.net @_rafaeltoledo Desenv. Android @Concrete Solutions concretesolutions.com.br/carreira