Dagger & Android

En af de primære fordele ved Dagger 2 i forhold til de fleste andre dependency injectionframeworks er, at dets strengt genererede implementering (ingen refleksion) betyder, at det kan bruges i Android-applikationer. Der er dog stadig nogle overvejelser, der skal gøres, når Dagger anvendes i Android-applikationer.

Filosofi

Selv om kode skrevet til Android er Java-kildekode, er den ofte helt anderledes med hensyn til stil. Typisk eksisterer sådanne forskelle for at imødekomme de unikke præstationshensyn på en mobil platform.

Men mange af de mønstre, der almindeligvis anvendes på kode beregnet til Android, er i modstrid med dem, der anvendes på anden Java-kode. Selv mange af rådene iEffective Java anses for at være uhensigtsmæssige for Android.

For at opnå målene om både idiomatisk og bærbar kode er Dagger afhængig af ProGuard til at efterbehandle den kompilerede bytekode. Dette gør det muligt for Dagger at udstede kildekode, der ser naturlig ud og føles naturlig på både serveren og Android, samtidig med at de forskellige værktøjskæder anvendes til at producere bytekode, der udføres effektivt i begge miljøer. Desuden har Dagger et eksplicit mål om at sikre, at den Java-kilde, som den genererer, konsekvent er kompatibel med ProGuard-optimeringer.

Det er naturligvis ikke alle problemer, der kan løses på denne måde, men det er den primære mekanisme, hvormed Android-specifik kompatibilitet vil blive tilvejebragt.

tl;dr

Dagger forudsætter, at brugere på Android vil bruge R8 eller ProGuard.

Hvorfor Dagger på Android er svært

En af de centrale vanskeligheder ved at skrive en Android-applikation med Dagger er, at mange Android-rammeklasser instantieres af operativsystemet selv, somActivity og Fragment, men Dagger fungerer bedst, hvis den kan oprette alle deinjicerede objekter. I stedet skal du udføre members injektion i en livscyklusmetode. Det betyder, at mange klasser ender med at se ud som:

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

Dette har et par problemer:

  1. Kopiering af kode gør det svært at refaktorisere senere. Efterhånden som flere og flere udviklere copy-paste denne blok, vil færre vide, hvad den egentlig gør.

  2. Mere grundlæggende kræver det, at den type, der anmoder om injektion (FrombulationActivity), kender til sin injektor. Selv om dette sker gennem interfaces i stedet for konkrete typer, bryder det et centralt princip for injektion af afhængighed: en klasse bør ikke vide noget om, hvordan den injiceres.

dagger.android

Klasserne i dagger.android tilbyder en metode til at forenkle ovenstående problemer. Det kræver, at du lærer nogle ekstra API’er og koncepter, men det giver dig reduceret boilerplate og injektion i dine Android-klasser på det rigtige sted i livscyklussen.

En anden fremgangsmåde er blot at bruge de normale Dagger API’er og følge vejledninger som den her.Det kan være enklere at forstå, men det har den ulempe, at man skal skrive ekstra boilerplate manuelt.

Jetpack- og Dagger-holdene arbejder sammen om et nyt initiativ til Dagger på Android, som forhåbentlig vil være et stort skift i forhold til den nuværende status quo. Selv om det desværre ikke er klar endnu, kan det være noget at overveje, når du vælger, hvordan du vil bruge Dagger i dine Android-projekter i dag.

Injicering af Activity-objekter

  1. Installer AndroidInjectionModule i din programkomponent for at sikre, at alle bindinger, der er nødvendige for disse basistyper, er tilgængelige.

  2. Start med at skrive en @Subcomponent, der implementererAndroidInjector<YourActivity>, med en@Subcomponent.Factory, der udviderAndroidInjector.Factory<YourActivity>:

    @Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
  3. Når du har defineret underkomponenten, tilføjes den til dit komponenthierarki ved at definere et modul, der binder underkomponentfabrikken, og ved at tilføje det til den komponent, der injicerer din 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: Hvis din underkomponent og dens fabrik ikke har andre metoder ellersupertyper end dem, der er nævnt i trin 2, kan du bruge@ContributesAndroidInjector til at generere dem for dig. I stedet for trin 2og 3 skal du tilføje en abstract modulmetode, der returnerer din aktivitet, anmærke den med @ContributesAndroidInjector og angive de moduler, du ønsker at installere i underkomponenten. Hvis underkomponenten har brug for scopes, skal du også anvende disse annotationer til metoden.

    @ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
  4. Næst skal du gøre din Application implementere HasAndroidInjectorog @Inject enDispatchingAndroidInjector<Object> toreturn fra androidInjector()-metoden:

    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. Endeligt skal du i din Activity.onCreate() metode kaldeAndroidInjection.inject(this)før du kalder super.onCreate();:

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

Hvordan virkede det?

AndroidInjection.inject() får en DispatchingAndroidInjector<Object> fra Application og videregiver din aktivitet til inject(Activity). DispatchingAndroidInjector slår AndroidInjector.Factory op for din aktivitets klasse (som er YourActivitySubcomponent.Factory), opretter AndroidInjector (som er YourActivitySubcomponent) og videregiver din aktivitet til inject(YourActivity).

Injicering af Fragmentobjekter

Injicering af en Fragment er lige så enkelt som injektion af en Activity. Definer din underkomponent på samme måde.

I stedet for at injicere i onCreate() som det gøres for Activitytyper, injiceres Fragments til i onAttach().

I modsætning til de moduler, der er defineret for Activitys, har du et valg af, hvor du vil installere moduler for Fragments. Du kan gøre din Fragment-komponent til en underkomponent til en anden Fragment-komponent, en Activity-komponent eller Application-komponenten – det afhænger alt sammen af, hvilke andre bindinger din Fragment kræver. Når du har besluttet dig for komponentens placering, skal du gøre det tilsvarende typeimplement HasAndroidInjector (hvis det ikke allerede gør det). Hvis din Fragment f.eks. har brug for bindinger fra YourActivitySubcomponent, vil din kode se nogenlunde sådan ud:

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

Da DispatchingAndroidInjector slår den relevanteAndroidInjector.Factory op af klassen ved kørselstid, kan en basisklasse både implementereHasAndroidInjector og kalde AndroidInjection.inject(). Det eneste, hver underklasse skal gøre, er at binde en tilsvarende @Subcomponent. Dagger leverer nogle få basistyper, der gør dette, f.eks. DaggerActivity og DaggerFragment,hvis du ikke har et kompliceret klassehierarki. Dagger indeholder også en DaggerApplication til samme formål – det eneste, du skal gøre, er at udvide den og overskrive applicationInjector()-metoden for at returnere den komponent, der skal injicere Application.

De følgende typer er også inkluderet:

  • DaggerService og DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

Bemærk: DaggerBroadcastReceiver bør kun bruges, nårBroadcastReceiver er registreret i AndroidManifest.xml. NårBroadcastReceiver oprettes i din egen kode, skal du i stedet foretrække konstruktørinjektion.

Støttebiblioteker

For brugere af Android-støttebiblioteket findes parallelle typer idagger.android.support-pakken.

TODO(ronshapiro): Vi bør begynde at opdele dette efter androidx-pakker

Hvordan får jeg fat i det?

Føj følgende til din 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'}

Hvornår skal man injicere

Konstruktørinjektion foretrækkes, når det er muligt, fordi javac vil sikre, at intet felt refereres, før det er blevet sat, hvilket er med til at undgåNullPointerExceptions. Når member-injektion er påkrævet (som diskuteret ovenfor), foretrækkes det at injicere så tidligt som muligt. Af denne grund kalder DaggerActivity AndroidInjection.inject() straks i onCreate(), før det kaldersuper.onCreate(), og DaggerFragment gør det samme i onAttach(), hvilket også forhindrer inkonsistenser, hvis Fragment er genindføjet.

Det er afgørende at kalde AndroidInjection.inject() før super.onCreate() i en Activity, da kaldet til super tilknytter Fragments fra den foregående aktivitetsinstans under konfigurationsændringen, som igen injicerer Fragments. For at Fragment-indsprøjtningen kan lykkes, skal Activity allerede være indsprøjtet. For brugere af ErrorProne er det en kompilerfejl at kalde AndroidInjection.inject() efter super.onCreate().

FAQ

Scoping AndroidInjector.Factory

AndroidInjector.Factory er beregnet til at være en tilstandsløs grænseflade, såimplementatorer ikke behøver at bekymre sig om at administrere tilstand i forbindelse med det objekt, som skal injiceres. Når DispatchingAndroidInjector anmoder om en AndroidInjector.Factory, sker det gennem en Provider, således at den ikke eksplicit beholder nogen instanser af fabrikken. Da nogle implementeringer kan indeholde en instans af den Activity/Fragment/etc., der injiceres, er det en kompileringsfejl at anvende et scope på de metoder, der leverer dem. Hvis du er sikker på, at din AndroidInjector.Factory ikke beholder en instans af det injicerede objekt, kan du undertrykke denne fejl ved at anvende@SuppressWarnings("dagger.android.ScopedInjectorFactory") på din modulmetode.