Dagger & Android

Jednou z hlavních výhod Daggeru 2 oproti většině ostatních dependency injectionframeworků je, že jeho striktně generovaná implementace (bez reflexe) znamená, že jej lze použít v aplikacích pro Android. Při použití Daggeru v rámci aplikací pro Android je však stále třeba zvážit některé okolnosti.

Filozofie

Přestože kód psaný pro Android je zdrojovým kódem Javy, je často zcela odlišný z hlediska stylu. Obvykle tyto rozdíly existují proto, aby se přizpůsobily jedinečným výkonnostním hlediskům mobilní platformy.

Mnoho vzorů, které se běžně používají u kódu určeného pro Android, je však v rozporu se vzory používanými u jiného kódu v Javě. Dokonce i mnoho rad v knize Efektivní Java je považováno za nevhodné pro Android.

Pro dosažení cílů idiomatického a přenositelného kódu se Dagger spoléhá na ProGuard, který následně zpracovává zkompilovaný bajtkód. To umožňuje systému Daggervytvářet zdrojový kód, který vypadá a působí přirozeně jak na serveru, tak v systému Android,a zároveň pomocí různých řetězců nástrojů vytvářet bajtový kód, který se efektivně spouští v obou prostředích. Dagger si navíc výslovně klade za cíl zajistit, aby zdrojový kód Javy, který generuje, byl důsledně kompatibilní s optimalizacemiProGuard.

Jistě, ne všechny problémy lze řešit tímto způsobem, ale je to hlavnímechanismus, kterým bude zajištěna kompatibilita specifická pro Android.

tl;dr

Dagger předpokládá, že uživatelé na Androidu budou používat R8 nebo ProGuard.

Proč je Dagger na Androidu těžký

Jednou z hlavních potíží při psaní aplikace pro Android pomocí Daggeru je, že mnoho tříd frameworku Androidu je instancováno samotným operačním systémem, napříkladActivity a Fragment, ale Dagger funguje nejlépe, pokud dokáže vytvořit všechnyinjektované objekty. Místo toho je třeba provést injekci členů v metodě životního cyklu. To znamená, že mnoho tříd nakonec vypadá takto:

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 }}

To má několik problémů:

  1. Kopírování kódu ztěžuje pozdější refaktorizaci. Jak bude tento blok kopírovat stále více vývojářů, méně jich bude vědět, co vlastně dělá.

  2. Ještě zásadnější je, že vyžaduje, aby typ požadující injection(FrombulationActivity) věděl o svém injektoru. I když se tak děje prostřednictvím rozhraní namísto konkrétních typů, porušuje to základní princip vstřikování závislostí: třída by neměla vědět nic o tom, jak je vstřikována.

dagger.android

Třídy v dagger.android nabízejí jeden přístup ke zjednodušení výše uvedenýchproblémů. Vyžaduje to naučit se některá další API a koncepty, ale získáteredukovaný boilerplate a injection ve vašich třídách Androidu na správném místě životního cyklu.

Dalším přístupem je prostě používat normální API Daggeru a postupovat podle návodů, jako je ten zde.To může být jednodušší na pochopení, ale přináší to nevýhodu v podobě nutnosti psát další boilerplate ručně.

Týmy Jetpack a Dagger spolupracují na nové iniciativě pro Dagger na Androidu, která má být velkým posunem oproti současnému stavu. I když bohužel ještě není hotová, může to být něco, co je třeba zvážit při rozhodování, jak používat Dagger ve vašich dnešních projektech pro Android.

Vložení objektů Activity

  1. Instalace AndroidInjectionModule do komponenty aplikace zajistí, že budou k dispozici všechny vazby potřebné pro tyto základní typy.

  2. Začněte tím, že napíšete @Subcomponent, který implementujeAndroidInjector<YourActivity>, pomocí@Subcomponent.Factory, který rozšiřujeAndroidInjector.Factory<YourActivity>:

    @Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
  3. Po definování podkomponenty ji přidejte do své hierarchie komponent definováním modulu, který váže továrnu podkomponenty, a přidáním do komponenty, která injektuje vaši 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: Pokud vaše subkomponenta a její továrna nemají jiné metody nebo nadtypy než ty, které jsou uvedeny v kroku 2, můžete použít@ContributesAndroidInjector, který je vygeneruje za vás. Místo kroků 2a 3 přidejte metodu modulu abstract, která vrátí vaši aktivitu, anotujte ji pomocí @ContributesAndroidInjector a zadejte moduly, které chcete do subkomponenty nainstalovat. Pokud subkomponenta potřebuje obory, použijte anotace oborů také na metodu.

    @ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
  4. Následujte, aby vaše Application implementovala HasAndroidInjectora @Inject aDispatchingAndroidInjector<Object> se vrátila z metody 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. Nakonec ve své metodě Activity.onCreate() zavolejteAndroidInjection.inject(this)před voláním super.onCreate();:

    public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
  6. Gratulujeme!

Jak to fungovalo?“

AndroidInjection.inject() získá DispatchingAndroidInjector<Object> z Application a předá vaši aktivitu inject(Activity). DispatchingAndroidInjector Vyhledá AndroidInjector.Factory pro třídu vašíaktivity (což je YourActivitySubcomponent.Factory), vytvoří AndroidInjector (což je YourActivitySubcomponent) a předá vašiaktivitu inject(YourActivity).

Vstřikování objektů Fragment

Vstřikování Fragment je stejně jednoduché jako vstřikování Activity. Stejným způsobem definujte i svou subkomponentu.

Místo injektování do onCreate(), jak je tomu u typů Activity, injektujte Fragment do onAttach().

Na rozdíl od modulů definovaných pro Activity máte u Fragment na výběr, kam moduly instalovat. Komponentu Fragment můžete vytvořit jako podsložku jiné komponenty Fragment, komponenty Activity nebo komponentyApplication – vše závisí na tom, jaké další vazby vaše Fragment vyžaduje. Po rozhodnutí o umístění komponenty vytvořte odpovídající typimplement HasAndroidInjector (pokud jej již nemá). Pokud například váš Fragmentpotřebuje vazby z YourActivitySubcomponent, bude váš kód vypadat nějak takto:

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 { ... }

Typy základního rámce

Protože DispatchingAndroidInjector za běhu vyhledá příslušnýAndroidInjector.Factory podle třídy, může základní třída implementovatHasAndroidInjector i volat AndroidInjection.inject(). Vše, co musí každá podtřída udělat, je svázat odpovídající @Subcomponent. Dagger poskytuje několik základních typů, které to dělají, například DaggerActivity a DaggerFragment,pokud nemáte složitou hierarchii tříd. Dagger také poskytuje DaggerApplication pro stejný účel – vše, co musíte udělat, je rozšířit jej a přepsat metodu applicationInjector() pro vrácení komponenty, která by měla injektovat Application.

Obsahuje také následující typy:

  • DaggerService a DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

Poznámka: DaggerBroadcastReceiver by se měl používat pouze tehdy, když jeBroadcastReceiver registrován v AndroidManifest.xml. Když jeBroadcastReceiver vytvořen ve vlastním kódu, upřednostněte místo toho konstrukční injekci.

Podpůrné knihovny

Pro uživatele podpůrné knihovny Androidu existují paralelní typy v balíčkudagger.android.support.

TODO(ronshapiro): Měli bychom to začít rozdělovat podle balíčků androidx

Jak to získat?

Přidejte do svého build.gradle následující:

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'}

Kdy injektovat

Kdykoli je to možné, dává se přednost injektování konstruktorů, protože javac zajistí, že na žádné pole nebude odkazováno dříve, než bylo nastaveno, což pomáhá vyhnout seNullPointerExceptions. Pokud je vyžadováno vstřikování členů (jak je uvedeno výše), dávejte přednost vstřikování co nejdříve. Z tohoto důvodu DaggerActivity volá AndroidInjection.inject() ihned v onCreate(), před volánímsuper.onCreate(), a DaggerFragment dělá totéž v onAttach(), což také zabraňuje nekonzistencím, pokud je Fragment znovu připojen.

Klíčové je volat AndroidInjection.inject() před super.onCreate() v Activity, protože volání super připojí Fragment z předchozí instance aktivity při změně konfigurace, která zase injektujeFragment. Aby bylo vstřikování Fragmentúspěšné, musí být Activityjiž vstříknuto. Pro uživatele ErrorProne je chybou kompilátoru volání AndroidInjection.inject() po super.onCreate().

FAQ

Scoping AndroidInjector.Factory

AndroidInjector.Factory má být bezstavovým rozhraním, aby seimplementátoři nemuseli starat o správu stavu souvisejícího s objektem, který bude injektován. Když DispatchingAndroidInjector požádá o AndroidInjector.Factory, učiní tak prostřednictvím Provider, takže explicitně neuchovává žádné instance továrny. Protože některé implementace mohouzachovat instanci Activity/Fragment/etc, která je injektována, je chybou kompilace použít obor na metody, které je poskytují. Pokud jste si jisti, že vaše AndroidInjector.Factory nezachovává instanci injektovaného objektu, můžete tuto chybu potlačit použitím@SuppressWarnings("dagger.android.ScopedInjectorFactory") na metodu modulu.