Dagger 2: DI eficiente em Android - TDC 2015
-
Upload
concrete-solutions -
Category
Technology
-
view
110 -
download
47
Transcript of Dagger 2: DI eficiente em Android - TDC 2015
Dagger 2Injeção de Dependência no Android
rafaeltoledo.net
@_rafaeltoledo
Desenv. Android @ Concrete Solutions
O que é Injeção de Dependência?
“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. (...)”
“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
?????????
Diz respeito a separação de onde os objetos são criados e onde são utilizados
Em vez de criar, você pede
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);
}
}
}
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);
}
}
}
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);
}
}
}
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);
}
}
}
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); }
}
}
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); }
}
}
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); }
}
}
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); }
}
}
UserDao
UserController
UserDao
UserController
SessionManager
UserDao
UserController
SessionManagerSignInController
UserDao
UserController
SessionManagerSignInController
CookieJob
JobManager
JobController
PermissionChecker
ReminderJob
RoleController
CandyShopper
FruitJuicerJob
UnknownController
UserDao
UserController
SessionManagerSignInController
CookieJob
JobManager
JobController
PermissionChecker
ReminderJob
RoleController
CandyShopper
FruitJuicerJob
UnknownController
EM TODO LUGAR!
class UserDaoImpl implements UserDao {
public UserDaoImpl() {
//...
}
}
class UserDaoImpl implements UserDao {
public UserDaoImpl(Context context) {
//...
}
}
Alteração em todas as classes!
Alteração em todas as classes!
Muito retrabalho!
Alteração em todas as classes!
Muito retrabalho!
Alteração em todas as classes!
Muito retrabalho!
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); }
}
}
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); }
}
}
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); }
}
}
DI: Separação do uso e criação de objetos
DI: Separação do uso e criação de objetos
não há a necessidade de bibliotecasou frameworks!
Desvantagem: muito boilerplate
Spring, Guice, Dagger 1 & cia
#oldbutgold
Spring, Guice, Dagger 1 & cia
#oldbutnot
Dagger 2
Dagger 2?
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
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
Dagger 2
● API enxuta!
● Código gerado é debugger-friendly
● Muito performático
● google.github.io/dagger
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();
}
// JSR-330
public @interface Inject {
}
public @interface Scope {
}
public @interface Qualifier {
}
Como integro no meu projeto?
// build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}
// build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}
// 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()
}
}
// 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()
}
}
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'
}
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'
}
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
}
E pronto!
Vamos começar?
@Inject
@Component @Module
@Provides
Dagger 2
@Inject
● Parte da JSR-330
● Marca quais dependências devem ser fornecidas pelo Dagger
● Pode aparecer de 3 formas no código
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
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)
1. No Construtor
● Limitação: não podemos anotar mais que um construtor com a anotação @Inject
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
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!
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
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);
}
3. Em métodos públicos
● O método anotado é chamado automaticamente logo após o construtor
@Module
● Parte da API do Dagger
● Utilizada para identificar classes que fornecem dependências
@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();
}
}
@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();
}
}
@Provide
● Utilizada nas classes anotadas como @Module para identificar quais métodos retornam dependências
@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();
}
}
@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();
}
}
@Component
● É a anotação que liga os @Modules aos @Injects
● É colocada em uma interface
● Define quais módulos possui, quem será injetado
@Component
● Precisa especificar obrigatoriamente seu escopo (ciclo de vida de suas dependências)
● Pode publicar dependências
● Pode possuir outros componentes
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@Singleton // Escopo
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@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();
}
@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*
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@ActivityScope // Escopo personalizado
@Component(
modules = TdcActivityModule.class,
dependencies = TdcComponent.class
)
public interface TdcActivityComponent {
TdcActivity inject(TdcActivity activity);
TdcActivityPresenter presenter();
}
@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);
}
}
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); }
}
}
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
Dagger 2Escopos dinâmicos
Escopo = ciclo de vida do grafo de dependências (componente)
@Singleton
@Component(
modules = {
DataModule.class,
UiModule.class
}
)
public interface TdcAppComponent {
void inject(TdcApplication app);
MainActivity inject(MainActivity activity);
Application getApplication();
AnalyticsManager getAnalyticsManager();
}
@ActivityScope // Escopo personalizado
@Component(
modules = TdcActivityModule.class,
dependencies = TdcComponent.class
)
public interface TdcActivityComponent {
TdcActivity inject(TdcActivity activity);
TdcActivityPresenter presenter();
}
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}
Definição de um escopo
Exemplo de uso
Miroslaw Stanek frogermcs.github.io
@Singleton
@Component(modules = DataModule.class)
public interface TdcAppComponent {
UserComponent plus(UserModule module);
// não é necessário expor as dependências
}
Implementação
@UserScope
@Subcomponent(modules = UserModule.class)
public interface UserComponent {
UserActivityComponent plus(UserActivityModule module);
}
Implementação
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
Dagger 2Outras coisas legais!
@Inject
Lazy<SharedPreferences> prefs;
void salvar() {
prefs.get().edit().putString("status", "ok!").apply();
}
Dependências “Preguiçosas”
// 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
// Declarando sua anotação
@MapKey(unwrapValue = true)
@interface GroupKey {
String value();
}
@MapKey
// 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
// 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
Ok! Tudo muito bonito mas...
O que eu ganho de fato com tudo isso?
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
Dagger 2 e Testes
● Programar orientado a interfaces - mocks
● Sobrescrita de Componentes e Módulos
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")
);
}
};
}
}
Dagger 2 e Testes
@Component(modules = DebugMainModule.class)
public interface TestMainComponent extends MainComponent {
}
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());
}
}
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);
}
}
Dagger 2github.com/rafaeltoledo/dagger2-tdc2015
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 Solutionsestamos contratando!
concretesolutions.com.br/carreira