Dagger & Android
Uma das principais vantagens do Dagger 2 sobre a maioria das outras estruturas de injeção de dependência é que sua implementação estritamente gerada (sem reflexão) significa que ele pode ser usado em aplicações Android. No entanto, ainda há algumas considerações a serem feitas quando se usa o Dagger dentro das aplicações Android.
Philosophy
Embora o código escrito para o Android seja fonte Java, muitas vezes são interms de estilo bem diferentes. Tipicamente, tais diferenças existem para acomodar as considerações de desempenho único de uma plataforma móvel.
Mas muitos dos padrões comumente aplicados ao código destinado ao Android são contrários aos aplicados a outros códigos Java. Mesmo muitos dos conselhos emEffective Java são considerados inadequados para o Android.
A fim de alcançar os objetivos tanto do código idiomático como do código portátil, Daggerrelies no ProGuard para pós-processar o bytecode compilado. Isto permite ao Dagger emitir código fonte que parece e parece natural tanto no servidor como no Android, enquanto usa as diferentes cadeias de ferramentas para produzir bytecode que executa eficientemente em ambos os ambientes. Além disso, Dagger tem um objetivo explícito de garantir que o fonte Java que ele gera seja consistentemente compatível com as otimizações doProGuard.
Obtendo-se claro, nem todas as questões podem ser tratadas dessa forma, mas é o primarimecanismo pelo qual a compatibilidade específica do Android será fornecida.
tl;dr
Dagger assume que usuários no Android usarão R8 ou ProGuard.
Por que Dagger no Android é difícil
Uma das dificuldades centrais de escrever um aplicativo Android usando Daggeris que muitas classes do framework Android são instanciadas pelo próprio SO, comoActivity
e Fragment
, mas Dagger funciona melhor se ele puder criar todos os objetos injetados. Ao invés disso, você tem que executar a injeção de membros em um método de ciclo de vida. Isto significa que muitas classes acabam parecendo:
public class FrombulationActivity extends Activity { @Inject Frombulator frombulator; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // DO THIS FIRST. Otherwise frombulator might be null! ((SomeApplicationBaseType) getContext().getApplicationContext()) .getApplicationComponent() .newActivityComponentBuilder() .activity(this) .build() .inject(this); // ... now you can write the exciting code }}
Isto tem alguns problemas:
-
Código de colagem de cópias torna difícil refactor mais tarde. Como mais e mais desenvolvedores copiam e colam esse bloco, menos saberão o que ele realmente faz.
-
Mais fundamentalmente, ele requer o tipo solicitando injeção(
FrombulationActivity
) para saber sobre seu injetor. Mesmo que isso seja feito através de interfaces ao invés de tipos concretos, ele quebra um princípio central de injeção de dependência: uma classe não deve saber nada sobre como ela é injetada.
dagger.android
As classes em dagger.android
oferecem uma abordagem para simplificar os problemas acima. Isto requer aprender algumas APIs e conceitos extras, mas dá a você uma redução na placa de caldeira e injeção em suas aulas Android no lugar certo no ciclo de vida.
Outra abordagem é apenas usar as APIs normais do Dagger e seguir guias como o Dagger onehehere.Isto pode ser mais simples de entender mas vem com a desvantagem de ter que escrever manualmente a placa de caldeira extra.
As equipes Jetpack e Dagger estão trabalhando juntas em uma nova iniciativa para o Dagger no Android que espera ser uma grande mudança a partir do statusquo atual. Embora infelizmente ainda não esteja pronto, isto pode ser algo a considerar quando escolher como usar o Dagger em seus projetos Android hoje.
Injeção de objetos de atividade
-
Instalar
AndroidInjectionModule
em seu componente de aplicação para garantir que todos os bindings necessários para estes tipos de base estejam disponíveis. -
Inicie escrevendo um
@Subcomponent
que implementeAndroidInjector<YourActivity>
, com um@Subcomponent.Factory
que se estendaAndroidInjector.Factory<YourActivity>
:@Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
-
Após definir o subcomponente, adicione-o à sua hierarquia de componentes, definindo um módulo que ligue a fábrica do subcomponente e adicionando-o ao componente que injecta o seu
Application
:@Module(subcomponents = YourActivitySubcomponent.class)abstract class YourActivityModule { @Binds @IntoMap @ClassKey(YourActivity.class) abstract AndroidInjector.Factory<?> bindYourAndroidInjectorFactory(YourActivitySubcomponent.Factory factory);}@Component(modules = {..., YourActivityModule.class})interface YourApplicationComponent { void inject(YourApplication application);}
Pro-tip: Se o seu subcomponente e a sua fábrica não têm outros métodos para além dos mencionados no passo #2, pode usar
@ContributesAndroidInjector
para os gerar para si. Ao invés dos passos 2 e 3, adicione um método de móduloabstract
que retorna sua atividade, anote com@ContributesAndroidInjector
, e especifique os módulos que você deseja instalar no subcomponente. Se o subcomponente precisar de escopos, aplique as anotações do escopo também ao método.@ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
-
Próximo, faça seu
Application
implementoHasAndroidInjector
e@Inject
aDispatchingAndroidInjector<Object>
toreturn a partir do métodoandroidInjector()
:public class YourApplication extends Application implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector; @Override public void onCreate() { super.onCreate(); DaggerYourApplicationComponent.create() .inject(this); } @Override public AndroidInjector<Object> androidInjector() { return dispatchingAndroidInjector; }}
-
Finalmente, no seu método
Activity.onCreate()
, ligueAndroidInjection.inject(this)
antes de ligarsuper.onCreate();
:public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
-
Congratulações!
Como funcionou?
AndroidInjection.inject()
obtém um DispatchingAndroidInjector<Object>
do Application
e passa a sua actividade para inject(Activity)
. A DispatchingAndroidInjector
procura na classe da youractivity o AndroidInjector.Factory
(que é YourActivitySubcomponent.Factory
), cria o AndroidInjector
(que é YourActivitySubcomponent
), e passa a youractivity para inject(YourActivity)
.
Injetando Fragmentos de objetos
Injetando um Fragment
é tão simples quanto injetar um Activity
. Defina o seu subcomponente da mesma forma.
Em vez de injectar em onCreate()
como é feito para Activity
tipos, injecte Fragment
s para em onAttach()
.
Não como os módulos definidos para Activity
s, tem a escolha de onde instalar os módulos para Fragment
s. Você pode fazer o seu Fragment
componente como subcomponente de outro Fragment
componente, um Activity
componente, ou o Application
componente – tudo depende de quais outros ligamentos o seu Fragment
exige. Depois de decidir sobre a localização do componente, faça o correspondente typeimplement HasAndroidInjector
(se ainda não o fez). Por exemplo, se o seu Fragment
necessárias bindings de YourActivitySubcomponent
, o seu código terá o seguinte aspecto:
public class YourActivity extends Activity implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> androidInjector; @Override public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); // ... } @Override public AndroidInjector<Object> androidInjector() { return androidInjector; }}public class YourFragment extends Fragment { @Inject SomeDependency someDep; @Override public void onAttach(Activity activity) { AndroidInjection.inject(this); super.onAttach(activity); // ... }}@Subcomponent(modules = ...)public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourFragment> {}}@Module(subcomponents = YourFragmentSubcomponent.class)abstract class YourFragmentModule { @Binds @IntoMap @ClassKey(YourFragment.class) abstract AndroidInjector.Factory<?> bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Factory factory);}@Subcomponent(modules = { YourFragmentModule.class, ... }public interface YourActivityOrYourApplicationComponent { ... }
Base Framework Types
Porque DispatchingAndroidInjector
procura o apropriadoAndroidInjector.Factory
pela classe em tempo de execução, uma classe base pode implementar HasAndroidInjector
assim como chamar AndroidInjection.inject()
. Tudo o que a eachsubclass precisa fazer é ligar um correspondente @Subcomponent
. O Dagger provê alguns tipos de base que fazem isso, tais como DaggerActivity
e DaggerFragment
, se você não tiver uma hierarquia de classes complicada. Dagger também fornece umDaggerApplication
para o mesmo propósito – tudo o que você precisa fazer é estendê-lo e sobrepor o método applicationInjector()
para retornar o componente que deve injetar o Application
.
Os seguintes tipos também estão incluídos:
-
DaggerService
eDaggerIntentService
-
DaggerBroadcastReceiver
DaggerContentProvider
Nota: DaggerBroadcastReceiver
só deve ser usado quando o BroadcastReceiver
estiver registrado no AndroidManifest.xml
. Quando oBroadcastReceiver
é criado em seu próprio código, prefira injeção do construtor.
Bibliotecas de suporte
Para usuários da biblioteca de suporte do Android, existem tipos paralelos nodagger.android.support
pacote.
TODO(ronshapiro): devemos começar a dividir isso por pacotes androidx
Como faço para obtê-lo?
Adicionar o seguinte ao seu build.gradle:
dependencies { implementation 'com.google.dagger:dagger-android:2.x' implementation 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries annotationProcessor 'com.google.dagger:dagger-android-processor:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x'}
Quando injectar
Injeção do construtor é preferível sempre que possível porque javac
assegurará que nenhum campo seja referenciado antes de ser definido, o que ajuda a evitarNullPointerException
s. Quando a injeção de membros é necessária (como discutido acima), prefira injetar o mais cedo possível. Por este motivo, DaggerActivity
chama AndroidInjection.inject()
imediatamente em onCreate()
, antes de chamarsuper.onCreate()
, e DaggerFragment
faz o mesmo em onAttach()
, o que também evita inconsistências se o Fragment
for recolocado.
é crucial chamar AndroidInjection.inject()
antes de super.onCreate()
inan Activity
, uma vez que a chamada para super
anexa Fragment
s da instância de atividade anterior durante a mudança de configuração, que por sua vez injeta oFragment
s. Para que o Fragment
injecção seja bem sucedida, o Activity
já deve ter sido injectado. Para usuários de ErrorProne, é erro do acompiler chamar AndroidInjection.inject()
após super.onCreate()
.
FAQ
Scoping AndroidInjector.Factory
AndroidInjector.Factory
é destinado a ser uma interface sem estado para que os seus usuários não tenham que se preocupar em gerenciar o estado relacionado com o objeto que será injetado. Quando DispatchingAndroidInjector
solicita umAndroidInjector.Factory
, ele o faz através de um Provider
para que ele não retenha textualmente quaisquer instâncias da fábrica. Como algumas implementações podem reter uma instância do Activity
/Fragment
/etc que está sendo injetado, é um erro de tempo de compilação para aplicar um escopo aos métodos que os fornecem. Se você tiver certeza de que seu AndroidInjector.Factory
não retém uma instância para o objeto injetado, você pode suprimir este erro aplicando@SuppressWarnings("dagger.android.ScopedInjectorFactory")
ao seu método do módulo.