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:
-
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.
-
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
-
Installer
AndroidInjectionModule
i din programkomponent for at sikre, at alle bindinger, der er nødvendige for disse basistyper, er tilgængelige. -
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> {}}
-
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 enabstract
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();
-
Næst skal du gøre din
Application
implementereHasAndroidInjector
og@Inject
enDispatchingAndroidInjector<Object>
toreturn fraandroidInjector()
-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; }}
-
Endeligt skal du i din
Activity.onCreate()
metode kaldeAndroidInjection.inject(this)
før du kaldersuper.onCreate();
:public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
-
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 Activity
typer, injiceres Fragment
s til i onAttach()
.
I modsætning til de moduler, der er defineret for Activity
s, har du et valg af, hvor du vil installere moduler for Fragment
s. 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
ogDaggerIntentService
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åNullPointerException
s. 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 Fragment
s fra den foregående aktivitetsinstans under konfigurationsændringen, som igen injicerer Fragment
s. 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.