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

Injeção de Dependência no Android com Dagger 2

Injeção de Dependência no Android com Dagger 2

Rafael Toledo

July 21, 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. (...)”
  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
  3. 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); } } }
  4. 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); } } }
  5. 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); } } }
  6. 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); } } }
  7. 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); } } }
  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); } } }
  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); } } }
  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); } } }
  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); } } }
  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); } } }
  13. 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); } } }
  14. DI: Separação do uso e criação de objetos não há

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

    • Muito performático • google.github.io/dagger
  18. 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(); }
  19. // build.gradle buildscript { repositories { jcenter() } dependencies {

    classpath 'com.android.tools.build:gradle:1.2.3' } } allprojects { repositories { jcenter() } }
  20. // build.gradle buildscript { repositories { jcenter() } dependencies {

    classpath 'com.android.tools.build:gradle:1.2.3' } } allprojects { repositories { jcenter() } }
  21. // build.gradle buildscript { repositories { jcenter() } dependencies {

    classpath 'com.android.tools.build:gradle:1.2.3' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.6' } } allprojects { repositories { jcenter() } }
  22. // build.gradle buildscript { repositories { jcenter() } dependencies {

    classpath 'com.android.tools.build:gradle:1.2.3' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.6' } } allprojects { repositories { jcenter() } }
  23. apply plugin: 'com.android.application' android { compileSdkVersion 22 buildToolsVersion '22.0.1' defaultConfig

    { applicationId 'net.rafaeltoledo.tdc2015' minSdkVersion 15 targetSdkVersion 22 versionCode 1 versionName '1.0' } } dependencies { compile 'com.android.support:appcompat-v7:22.2.1' }
  24. apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' android { compileSdkVersion 22

    buildToolsVersion '22.0.1' defaultConfig { applicationId 'net.rafaeltoledo.tdc2015' minSdkVersion 15 targetSdkVersion 22 versionCode 1 versionName '1.0' } } dependencies { compile 'com.android.support:appcompat-v7:22.2.1' compile 'com.google.dagger:dagger:2.0.1' apt 'com.google.dagger:dagger-compiler:2.0.1' provided 'org.glassfish:javax.annotation:10.0-b28' }
  25. apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' android { compileSdkVersion 22

    buildToolsVersion '22.0.1' defaultConfig { applicationId 'net.rafaeltoledo.tdc2015' minSdkVersion 15 targetSdkVersion 22 versionCode 1 versionName '1.0' } } dependencies { compile 'com.android.support:appcompat-v7:22.2.1' compile 'com.google.dagger:dagger:2.0.1' apt 'com.google.dagger:dagger-compiler:2.0.1' provided 'org.glassfish:javax.annotation:10.0-b28' // JSR-330 }
  26. @Inject • Parte da JSR-330 • Marca quais dependências devem

    ser fornecidas pelo Dagger • Pode aparecer de 3 formas no código
  27. 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
  28. 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)
  29. 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
  30. 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!
  31. 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
  32. 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); }
  33. 3. Em métodos públicos • O método anotado é chamado

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

    identificar classes que fornecem dependências
  35. @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(); } }
  36. @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(); } }
  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(); } }
  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(); } }
  39. @Component • É a anotação que liga os @Modules aos

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

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

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

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

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

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

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

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

    TdcComponent.class ) public interface TdcActivityComponent { TdcActivity inject(TdcActivity activity); TdcActivityPresenter presenter(); }
  53. 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
  54. // 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
  55. // 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
  56. // 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
  57. 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
  58. Dagger 2 e Testes • Programar orientado a interfaces -

    mocks • Sobrescrita de Componentes e Módulos
  59. 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") ); } }; } }
  60. 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()); } }
  61. 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); } }
  62. 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