Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Better, Faster, Stronger, Dagger

Better, Faster, Stronger, Dagger

Apresentação realizada no TDC Porto Alegre 2015

Rafael Toledo

September 25, 2015
Tweet

More Decks by Rafael Toledo

Other Decks in Programming

Transcript

  1. “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
  2. “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
  3. Diz respeito a separação de onde os objetos são criados

    e onde são utilizados @_rafaeltoledo
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. DI: Separação do uso e criação de objetos não há

    a necessidade de bibliotecas ou frameworks! @_rafaeltoledo
  16. 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
  17. 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
  18. Dagger 2 • API enxuta! • Código gerado é debugger-friendly

    • Muito performático • google.github.io/dagger @_rafaeltoledo
  19. 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
  20. // JSR-330 public @interface Inject { } public @interface Scope

    { } public @interface Qualifier { } @_rafaeltoledo
  21. // build.gradle buildscript { repositories { jcenter() } dependencies {

    classpath 'com.android.tools.build:gradle:1.3.1' } } allprojects { repositories { jcenter() } } @_rafaeltoledo
  22. // build.gradle buildscript { repositories { jcenter() } dependencies {

    classpath 'com.android.tools.build:gradle:1.3.1' } } allprojects { repositories { jcenter() } } @_rafaeltoledo
  23. // 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
  24. 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
  25. 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
  26. 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
  27. @Inject • Parte da JSR-330 • Marca quais dependências devem

    ser fornecidas pelo Dagger • Pode aparecer de 3 formas no código @_rafaeltoledo
  28. 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
  29. 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
  30. 1. No Construtor • Limitação: não podemos anotar mais que

    um construtor com a anotação @Inject @_rafaeltoledo
  31. 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
  32. 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
  33. 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
  34. 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
  35. 3. Em métodos públicos • O método anotado é chamado

    automaticamente logo após o construtor @_rafaeltoledo
  36. @Module • Parte da API do Dagger • Utilizada para

    identificar classes que fornecem dependências @_rafaeltoledo
  37. @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
  38. @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
  39. @Provides • Utilizada nas classes anotadas como @Module para identificar

    quais métodos retornam dependências @_rafaeltoledo
  40. @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
  41. @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
  42. @Component • É a anotação que liga os @Modules aos

    @Injects • É colocada em uma interface • Define quais módulos possui, quem será injetado @_rafaeltoledo
  43. @Component • Precisa especificar obrigatoriamente seu escopo (ciclo de vida

    de suas dependências) • Pode publicar dependências • Pode possuir outros componentes @_rafaeltoledo
  44. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public

    interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  45. @Singleton // Escopo @Component( modules = { DataModule.class, UiModule.class }

    ) public interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  46. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public

    interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  47. @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
  48. @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
  49. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public

    interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  50. @ActivityScope // Escopo personalizado @Component( modules = TdcActivityModule.class, dependencies =

    TdcComponent.class ) public interface TdcActivityComponent { TdcActivity inject(TdcActivity activity); TdcActivityPresenter presenter(); } @_rafaeltoledo
  51. @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
  52. 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
  53. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public

    interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); } @_rafaeltoledo
  54. @ActivityScope // Escopo personalizado @Component( modules = TdcActivityModule.class, dependencies =

    TdcComponent.class ) public interface TdcActivityComponent { TdcActivity inject(TdcActivity activity); TdcActivityPresenter presenter(); } @_rafaeltoledo
  55. @Singleton @Component(modules = DataModule.class) public interface TdcAppComponent { UserComponent plus(UserModule

    module); // não é necessário expor as dependências } Implementação @_rafaeltoledo
  56. 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
  57. // 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
  58. // 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
  59. // 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
  60. 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
  61. Dagger 2 e Testes • Programar orientado a interfaces -

    mocks • Sobrescrita de Componentes e Módulos @_rafaeltoledo
  62. 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
  63. 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
  64. 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
  65. 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