Dagger 2: DI eficiente em Android - TDC 2015

  • Published on
    12-Aug-2015

  • View
    107

  • Download
    47

Transcript

  1. 1. Dagger 2 Injeo de Dependncia no Android
  2. 2. rafaeltoledo.net @_rafaeltoledo Desenv. Android @ Concrete Solutions
  3. 3. O que Injeo de Dependncia?
  4. 4. Injeo de Dependncia (ou Dependency Injection, em ingls) um padro de desenvolvimento de programas de computadores utilizado quando necessrio manter baixo o nvel de acoplamento entre diferentes mdulos de um sistema. (...)
  5. 5. Nesta soluo as dependncias entre os mdulos no so definidas programaticamente, mas sim pela configurao de uma infraestrutura de software (container) que responsvel por "injetar" em cada componente suas dependncias declaradas. A Injeo de dependncia se relaciona com o padro Inverso de controle mas no pode ser considerada um sinnimo deste. Wikipedia, 2015
  6. 6. ?????????
  7. 7. Diz respeito a separao de onde os objetos so criados e onde so utilizados
  8. 8. Em vez de criar, voc pede
  9. 9. 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); } } }
  10. 10. 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); } } }
  11. 11. 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); } } }
  12. 12. 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); } } }
  13. 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); } } }
  14. 14. 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); } } }
  15. 15. 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); } } }
  16. 16. 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); } } }
  17. 17. UserDao UserController
  18. 18. UserDao UserController SessionManager
  19. 19. UserDao UserController SessionManager SignInController
  20. 20. UserDao UserController SessionManager SignInController CookieJob JobManager JobController PermissionChecker ReminderJob RoleController CandyShopper FruitJuicerJob UnknownController
  21. 21. UserDao UserController SessionManager SignInController CookieJob JobManager JobController PermissionChecker ReminderJob RoleController CandyShopper FruitJuicerJob UnknownController EM TODO LUGAR!
  22. 22. class UserDaoImpl implements UserDao { public UserDaoImpl() { //... } }
  23. 23. class UserDaoImpl implements UserDao { public UserDaoImpl(Context context) { //... } }
  24. 24. Alterao em todas as classes!
  25. 25. Alterao em todas as classes! Muito retrabalho!
  26. 26. Alterao em todas as classes! Muito retrabalho!
  27. 27. Alterao em todas as classes! Muito retrabalho!
  28. 28. 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); } } }
  29. 29. 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); } } }
  30. 30. 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); } } }
  31. 31. DI: Separao do uso e criao de objetos
  32. 32. DI: Separao do uso e criao de objetos no h a necessidade de bibliotecas ou frameworks!
  33. 33. Desvantagem: muito boilerplate
  34. 34. Spring, Guice, Dagger 1 & cia #oldbutgold
  35. 35. Spring, Guice, Dagger 1 & cia #oldbutnot
  36. 36. Dagger 2
  37. 37. Dagger 2?
  38. 38. Dagger 2 Fork do Dagger, da Square, feito pela Google Elimina todo o uso de reflections Construda sobre as anotaes javax. inject da especificao JSR-330
  39. 39. Dagger 2 Validao de todo o grafo de dependncias em tempo de compilao Menos flexvel, se comparado ao Dagger 1 - ex.: no possui module overriding Proguard configuration-free
  40. 40. Dagger 2 API enxuta! Cdigo gerado debugger-friendly Muito performtico google.github.io/dagger
  41. 41. 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 get(); }
  42. 42. // JSR-330 public @interface Inject { } public @interface Scope { } public @interface Qualifier { }
  43. 43. Como integro no meu projeto?
  44. 44. // build.gradle buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.2.3' } } allprojects { repositories { jcenter() } }
  45. 45. // build.gradle buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.2.3' } } allprojects { repositories { jcenter() } }
  46. 46. // 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() } }
  47. 47. // 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() } }
  48. 48. 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' }
  49. 49. 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' }
  50. 50. 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 }
  51. 51. E pronto!
  52. 52. Vamos comear?
  53. 53. @Inject @Component @Module @Provides Dagger 2
  54. 54. @Inject Parte da JSR-330 Marca quais dependncias devem ser fornecidas pelo Dagger Pode aparecer de 3 formas no cdigo
  55. 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
  56. 56. 1. No Construtor Todas as dependncias vem do grafo de dependncias do Dagger Anotar uma classe dessa forma faz com que ela tambm faa parte do grafo de dependncias (podendo ser injetada em outras classes)
  57. 57. 1. No Construtor Limitao: no podemos anotar mais que um construtor com a anotao @Inject
  58. 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
  59. 59. 2. Nos Atributos da Classe A injeo nesse caso deve ser manual, caso contrrios os atributos sero todos nulos @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); getComponent().inject(this); } Limitao: os atributos no podem ser privados!
  60. 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 mtodos pblicos
  61. 61. 3. Em mtodos pblicos Utilizado em conjunto com a anotao no construtor da classe Para casos onde o objeto injetado necessitamos da instncia da prpria classe na dependncia fornecida @Inject public void enableAnalytics(AnalyticsManager analytics) { analytics.track(this); }
  62. 62. 3. Em mtodos pblicos O mtodo anotado chamado automaticamente logo aps o construtor
  63. 63. @Module Parte da API do Dagger Utilizada para identificar classes que fornecem dependncias
  64. 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(); } }
  65. 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(); } }
  66. 66. @Provide Utilizada nas classes anotadas como @Module para identificar quais mtodos retornam dependncias
  67. 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(); } }
  68. 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(); } }
  69. 69. @Component a anotao que liga os @Modules aos @Injects colocada em uma interface Define quais mdulos possui, quem ser injetado
  70. 70. @Component Precisa especificar obrigatoriamente seu escopo (ciclo de vida de suas dependncias) Pode publicar dependncias Pode possuir outros componentes
  71. 71. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); }
  72. 72. @Singleton // Escopo @Component( modules = { DataModule.class, UiModule.class } ) public interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); }
  73. 73. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); }
  74. 74. @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(); }
  75. 75. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); // Dependncias visveis } // para outros componentes*
  76. 76. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); }
  77. 77. @ActivityScope // Escopo personalizado @Component( modules = TdcActivityModule.class, dependencies = TdcComponent.class ) public interface TdcActivityComponent { TdcActivity inject(TdcActivity activity); TdcActivityPresenter presenter(); }
  78. 78. @Generated("dagger.internal.codegen.ComponentProcessor") public final class DaggerMainComponent implements MainComponent { private Provider provideApiServiceProvider; private MembersInjector 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); } }
  79. 79. 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); } } }
  80. 80. public class TdcApp extends Application { TdcAppComponent component; @Override public void onCreate() { component = DaggerTdcAppComponent.create(); component = DaggerTdcAppComponent.builder() .dataModule(new DataModule(this)) // Mdulo com construtor .build(); // parametrizado } } Instanciando um Componente
  81. 81. Dagger 2 Escopos dinmicos
  82. 82. Escopo = ciclo de vida do grafo de dependncias (componente)
  83. 83. @Singleton @Component( modules = { DataModule.class, UiModule.class } ) public interface TdcAppComponent { void inject(TdcApplication app); MainActivity inject(MainActivity activity); Application getApplication(); AnalyticsManager getAnalyticsManager(); }
  84. 84. @ActivityScope // Escopo personalizado @Component( modules = TdcActivityModule.class, dependencies = TdcComponent.class ) public interface TdcActivityComponent { TdcActivity inject(TdcActivity activity); TdcActivityPresenter presenter(); }
  85. 85. @Scope @Retention(RetentionPolicy.RUNTIME) public @interface UserScope { } Definio de um escopo
  86. 86. Exemplo de uso Miroslaw Stanek frogermcs.github.io
  87. 87. @Singleton @Component(modules = DataModule.class) public interface TdcAppComponent { UserComponent plus(UserModule module); // no necessrio expor as dependncias } Implementao
  88. 88. @UserScope @Subcomponent(modules = UserModule.class) public interface UserComponent { UserActivityComponent plus(UserActivityModule module); } Implementao
  89. 89. 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; } ... } Implementao
  90. 90. Dagger 2 Outras coisas legais!
  91. 91. @Inject Lazy prefs; void salvar() { prefs.get().edit().putString("status", "ok!").apply(); } Dependncias Preguiosas
  92. 92. // No mdulo @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
  93. 93. // Declarando sua anotao @MapKey(unwrapValue = true) @interface GroupKey { String value(); } @MapKey
  94. 94. // Fornecendo dependncias no mdulo @Provides(type = Type.MAP) @GroupKey("um") String provideFirstValue() { return "primeiro valor"; } @Provides(type = Type.MAP) @GroupKey("dois") String provideSecondValue() { return "segundo valor"; } @MapKey
  95. 95. // Uso @Inject Map map; // {um=primeiro valor, dois=segundo valor} Por enquanto, s aceita Map e Set, e valores do tipo String e Enumeradores @MapKey