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:

  1. 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.

  2. 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

  1. Instalar AndroidInjectionModule em seu componente de aplicação para garantir que todos os bindings necessários para estes tipos de base estejam disponíveis.

  2. 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> {}}
  3. 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ódulo abstract 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();
  4. Próximo, faça seu Application implemento HasAndroidInjector e @Inject aDispatchingAndroidInjector<Object> toreturn a partir do método androidInjector():

    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; }}
  5. Finalmente, no seu método Activity.onCreate(), ligueAndroidInjection.inject(this)antes de ligar super.onCreate();:

    public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
  6. 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 Activitytipos, injecte Fragments para em onAttach().

Não como os módulos definidos para Activitys, tem a escolha de onde instalar os módulos para Fragments. 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 Fragmentnecessá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 e DaggerIntentService
  • 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 evitarNullPointerExceptions. 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 Fragments da instância de atividade anterior durante a mudança de configuração, que por sua vez injeta oFragments. 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.