Dagger și Android

Unul dintre principalele avantaje ale lui Dagger 2 față de majoritatea celorlalte framework-uri de injecție a dependențelor este că implementarea sa strict generată (fără reflexie) înseamnă că poate fi utilizată în aplicațiile Android. Cu toate acestea, există încă uneleconsiderații care trebuie luate în considerare atunci când se utilizează Dagger în cadrul aplicațiilor Android.

Filosofie

În timp ce codul scris pentru Android este sursă Java, acesta este adesea destul de diferit din punct de vedere al stilului. De obicei, astfel de diferențe există pentru a acomoda considerațiile unice de performanță ale unei platforme mobile.

Dar multe dintre modelele aplicate în mod obișnuit codului destinat Android suntcontrarii celor aplicate la alt cod Java. Chiar și o mare parte din sfaturile dinEffective Java sunt considerate nepotrivite pentru Android.

Pentru a atinge obiectivele unui cod atât idiomatic cât și portabil, Daggerse bazează pe ProGuard pentru a post-procesa codul byte compilat. Acest lucru îi permite lui Dagger să emită o sursă care arată și se simte natural atât pe server cât și pe Android,folosind în același timp diferite toolchains pentru a produce bytecode care se executăeficient în ambele medii. Mai mult, Dagger are un obiectiv explicit de a se asigura că sursa Java pe care o generează este în mod constant compatibilă cu optimizărileProGuard.

Desigur, nu toate problemele pot fi abordate în acest mod, dar acesta este principalul mecanism prin care va fi asigurată compatibilitatea specifică Android.

tl;dr

Dagger presupune că utilizatorii de pe Android vor folosi R8 sau ProGuard.

De ce Dagger pe Android este dificil

Una dintre dificultățile centrale ale scrierii unei aplicații Android folosind Dagger este că multe clase din cadrul Android sunt instanțiate de sistemul de operare însuși, cum ar fiActivity și Fragment, dar Dagger funcționează cel mai bine dacă poate crea toate obiectele injectate. În schimb, trebuie să efectuați injectarea membrilor într-o metodă din ciclul de viață. Acest lucru înseamnă că multe clase sfârșesc prin a arăta ca:

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

Acest lucru are câteva probleme:

  1. Copierea de cod face dificilă refactorizarea ulterioară. Pe măsură ce tot mai mulțidezvoltatori copiază-lipesc acel bloc, tot mai puțini vor ști ce face de fapt.

  2. Mai fundamental, este nevoie ca tipul care solicită injectarea(FrombulationActivity) să știe despre injectorul său. Chiar dacă acest lucru se face prin interfețe în loc de tipuri concrete, se încalcă un principiu de bază al injecției de dependență: o clasă nu ar trebui să știe nimic despre modul în care este injectată.

dagger.android

Classele din dagger.android oferă o abordare pentru a simplifica problemele de mai sus. Aceasta necesită învățarea unor API-uri și concepte suplimentare, dar vă oferă un boilerplate redus și injecție în clasele Android la locul potrivit în ciclul de viață.

O altă abordare este de a folosi doar API-urile Dagger normale și de a urma ghiduri precum cel de aici.Acest lucru poate fi mai simplu de înțeles, dar vine cu dezavantajul de a trebui să scrie manual boilerplate suplimentar.

Echipele Jetpack și Dagger lucrează împreună la o nouă inițiativăpentru Dagger pe Android care speră să fie o schimbare mare față de statusquo actual. Deși, din nefericire, nu este încă gata, acest lucru poate fi ceva de luat în considerare atunci când alegeți cum să folosiți Dagger în proiectele dvs. Android astăzi.

Injectarea obiectelor Activity

  1. Instalați AndroidInjectionModule în componenta aplicației dvs. pentru a vă asigura că toate legăturile necesare pentru aceste tipuri de bază sunt disponibile.

  2. Începeți prin a scrie un @Subcomponent care implementeazăAndroidInjector<YourActivity>, cu un@Subcomponent.Factory care extindeAndroidInjector.Factory<YourActivity>:

    @Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
  3. După ce ați definit subcomponenta, adăugați-o la ierarhia componentei dvs. prin definirea unui modul care leagă fabrica subcomponentei și adăugarea acestuia la componenta care injectează 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: Dacă subcomponenta dvs. și fabrica sa nu au alte metode sau supratipuri în afară de cele menționate la pasul 2, puteți folosi@ContributesAndroidInjector pentru a le genera pentru dvs. În loc de pașii 2și 3, adăugați o metodă de modul abstract care returnează activitatea dumneavoastră, adnotați-o cu @ContributesAndroidInjector și specificați modulele pe care doriți să le instalați în subcomponentă. Dacă subcomponenta are nevoie de domenii de cuprindere, aplicați adnotările de domeniu și metodei.

    @ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
  4. În continuare, faceți ca Application să implementeze HasAndroidInjectorși @Inject unDispatchingAndroidInjector<Object> să returneze din metoda 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. În cele din urmă, în metoda dvs. Activity.onCreate(), apelațiAndroidInjection.inject(this)înainte de a apela super.onCreate();:

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

Cum a funcționat?

AndroidInjection.inject() primește un DispatchingAndroidInjector<Object> de la Application și transmite activitatea ta la inject(Activity). DispatchingAndroidInjector caută AndroidInjector.Factory pentru clasa AndroidInjector.Factory a activității tale (care este YourActivitySubcomponent.Factory), creează AndroidInjector (care este YourActivitySubcomponent) și trece activitatea ta la inject(YourActivity).

Injectarea obiectelor Fragment

Injectarea unui Fragment este la fel de simplă ca și injectarea unui Activity. Definiți-vă subcomponentul în același mod.

În loc să injectați în onCreate(), așa cum se face pentru tipurile Activity, injectați Fragments la în onAttach().

În mod diferit de modulele definite pentru Activitys, aveți posibilitatea de a alege unde să instalați modulele pentru Fragments. Puteți face din componenta Fragment ca subcomponentă a unei alte componente Fragment, a unei componente Activity sau a componenteiApplication – totul depinde de ce alte legături necesită Fragmentul dumneavoastră. După ce v-ați hotărât asupra locației componentei, faceți ca tipul corespunzător să fie implementat HasAndroidInjector (dacă nu o face deja). De exemplu, dacă Fragmentnecesită legături de la YourActivitySubcomponent, codul dvs. va arăta cam așa:

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

Tipuri din cadrul de bază

Pentru că DispatchingAndroidInjector caută la execuție AndroidInjector.Factory corespunzătoare de către clasă, o clasă de bază poate implementaHasAndroidInjector, precum și apela AndroidInjection.inject(). Tot ce trebuie să facă fiecare subclasă este să lege un @Subcomponent corespunzător. Dagger oferă câteva tipuri de bază care fac acest lucru, cum ar fi DaggerActivity și DaggerFragment, în cazul în care nu aveți o ierarhie complicată a claselor. Dagger oferă, de asemenea, un DaggerApplication pentru același scop – tot ce trebuie să faceți este să îl extindeți și să suprascrieți metoda applicationInjector() pentru a returna componenta care ar trebui să injecteze Application.

Sunt incluse, de asemenea, următoarele tipuri:

  • DaggerService și DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

Nota: DaggerBroadcastReceiverar trebui să fie folosit numai atunci cândBroadcastReceiver este înregistrat în AndroidManifest.xml. Atunci cândBroadcastReceiver este creat în propriul cod, preferați injectarea constructorului în locul acestuia.

Biblioteci de suport

Pentru utilizatorii bibliotecii de suport Android, tipurile paralele există în pachetuldagger.android.support.

TODO(ronshapiro): ar trebui să începem să împărțim acest lucru pe pachete androidx

Cum îl obținem?

Adaugați următoarele la 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'}

Când să injectați

Injectarea constructorului este preferată ori de câte ori este posibil, deoarece javacse va asiguracă niciun câmp nu este referit înainte de a fi setat, ceea ce ajută la evitareaNullPointerExceptionlor. Atunci când este necesară injectarea membrilor (așa cum s-a discutat mai sus), este de preferat să se injecteze cât mai devreme posibil. Din acest motiv, DaggerActivityapela AndroidInjection.inject() imediat în onCreate(), înainte de a apelasuper.onCreate(), iar DaggerFragment face același lucru în onAttach(), ceea ce previne, de asemenea, inconsecvențele în cazul în care Fragment este reatașat.

Este crucial să se apeleze AndroidInjection.inject() înainte de super.onCreate() într-un Activity, deoarece apelul la super atașează Fragments din instanța de activitate anterioară în timpul schimbării configurației, care la rândul său injectează Fragments. Pentru ca injectarea Fragment să reușească, Activitytrebuie să fie deja injectată. Pentru utilizatorii ErrorProne, este o eroare de compilator să apeleze AndroidInjection.inject() după super.onCreate().

FAQ

Scoping AndroidInjector.Factory

AndroidInjector.Factory este menită să fie o interfață fără stare, astfel încât cei care o implementează nu trebuie să se preocupe de gestionarea stării legate de obiectul care va fi injectat. Atunci când DispatchingAndroidInjector solicită unAndroidInjector.Factory, o face prin intermediul unui Provider, astfel încât să nu rețină în mod explicit nicio instanță a fabricii. Deoarece unele implementări pot reține o instanță a Activity/Fragment/etc. care este injectat, este o eroare de compilare să se aplice un domeniu de aplicare la metodele care le furnizează. Dacă sunteți sigur că AndroidInjector.Factory dvs. nu reține o instanță a obiectului injectat, puteți elimina această eroare aplicând@SuppressWarnings("dagger.android.ScopedInjectorFactory") la metoda dvs. de modul.

.