Dagger & Android

Uno dei principali vantaggi di Dagger 2 rispetto alla maggior parte degli altri dependency injectionframeworks è che la sua implementazione strettamente generata (nessuna riflessione) significa che può essere usata nelle applicazioni Android. Tuttavia, ci sono ancora alcune considerazioni da fare quando si usa Dagger nelle applicazioni Android.

Filosofia

Mentre il codice scritto per Android è sorgente Java, è spesso abbastanza diverso nello stile. Tipicamente, tali differenze esistono per accomodare le considerazioni uniche sulle prestazioni di una piattaforma mobile.

Ma molti dei modelli comunemente applicati al codice destinato ad Android sono in contrasto con quelli applicati ad altro codice Java. Anche molti dei consigli in Java efficace sono considerati inappropriati per Android.

Per raggiungere gli obiettivi di un codice idiomatico e portabile, Dagger si affida a ProGuard per il post-processing del bytecode compilato. Questo permette a Dagger di emettere codice sorgente che appare e si sente naturale sia sul server che su Android, mentre utilizza le diverse catene di strumenti per produrre bytecode che viene eseguito in modo efficiente in entrambi gli ambienti. Inoltre, Dagger ha l’obiettivo esplicito di assicurare che il sorgente Java che genera sia costantemente compatibile con le ottimizzazioni di ProGuard.

Ovviamente, non tutti i problemi possono essere affrontati in questo modo, ma è il meccanismo principale con cui verrà fornita la compatibilità specifica per Android.

tl;dr

Dagger presume che gli utenti su Android useranno R8 o ProGuard.

Perché Dagger su Android è difficile

Una delle difficoltà centrali nello scrivere un’applicazione Android usando Dagger è che molte classi del framework Android sono istanziate dal sistema operativo stesso, comeActivity e Fragment, ma Dagger funziona meglio se può creare tutti gli oggetti iniettati. Invece, è necessario eseguire l’iniezione dei membri in un lifecyclelemethod. Questo significa che molte classi finiscono per assomigliare a:

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

Questo ha alcuni problemi:

  1. Copiare il codice rende difficile il refactoring in seguito. Man mano che sempre più sviluppatori copia-incollano quel blocco, meno sapranno cosa fa realmente.

  2. Più fondamentalmente, richiede che il tipo che richiede l’iniezione (FrombulationActivity) conosca il suo iniettore. Anche se questo viene fatto attraverso interfacce invece di tipi concreti, rompe un principio fondamentale dell’iniezione di dipendenza: una classe non dovrebbe sapere nulla di come viene iniettata.

dagger.android

Le classi in dagger.android offrono un approccio per semplificare i problemi di cui sopra. Questo richiede l’apprendimento di alcune API e concetti extra, ma ti dà un boilerplate ridotto e l’iniezione nelle tue classi Android al posto giusto nel ciclo di vita.

Un altro approccio è quello di usare semplicemente le normali API Dagger e seguire guide come quella qui.Questo può essere più semplice da capire, ma ha il rovescio della medaglia di dover scrivere manualmente boilerplate extra.

I team di Jetpack e Dagger stanno lavorando insieme su una nuova iniziativa per Dagger su Android che spera di essere un grande cambiamento rispetto allo statusquo attuale. Anche se purtroppo non è ancora pronto, questo può essere qualcosa da considerare quando si sceglie come usare Dagger nei propri progetti Android oggi.

Iniezione di oggetti Activity

  1. Installare AndroidInjectionModule nel componente della propria applicazione per garantire che tutti i binding necessari per questi tipi di base siano disponibili.

  2. Inizia scrivendo un @Subcomponent che implementaAndroidInjector<YourActivity>, con un@Subcomponent.Factory che estendeAndroidInjector.Factory<YourActivity>:

    @Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
  3. Dopo aver definito il sottocomponente, aggiungetelo alla vostra gerarchia di componenti definendo un modulo che lega la fabbrica del sottocomponente e aggiungendolo al componente che inietta il vostro 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 il vostro sottocomponente e la sua fabbrica non hanno altri metodi o supertipi oltre a quelli menzionati nel passo #2, potete usare@ContributesAndroidInjector per generarli per voi. Invece dei passi 2 e 3, aggiungete un metodo modulo abstract che restituisca la vostra attività, annotatelo con @ContributesAndroidInjector, e specificate i moduli che volete installare nel sottocomponente. Se il sottocomponente ha bisogno di scope, applicate le annotazioni di scope anche al metodo.

    @ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
  4. Poi, fate che il vostro Application implementi HasAndroidInjectore @Inject unDispatchingAndroidInjector<Object> per ritornare dal metodo 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. Infine, nel tuo metodo Activity.onCreate(), chiamaAndroidInjection.inject(this) prima di chiamare super.onCreate();:

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

Come ha funzionato?

AndroidInjection.inject() ottiene un DispatchingAndroidInjector<Object> dal Application e passa la tua attività a inject(Activity). IlDispatchingAndroidInjector cerca il AndroidInjector.Factory per la classe della tua attività (che è YourActivitySubcomponent.Factory), crea il AndroidInjector (che è YourActivitySubcomponent), e passa la tua attività a inject(YourActivity).

Iniezione di oggetti Fragment

Iniettare un Fragment è semplice come iniettare un Activity. Definite il vostro sottocomponente nello stesso modo.

Invece di iniettare in onCreate() come si fa per i Activitytipi, iniettate i Fragment in onAttach().

A differenza dei moduli definiti per i Activity, avete una scelta di dove installare i moduli per i Fragment. Puoi fare il tuo componente Fragment come sottocomponente di un altro componente Fragment, un componente Activity, o il componente Application – tutto dipende da quali altri binding richiede il tuo Fragment. Dopo aver deciso la posizione del componente, rendete il corrispondente typeimplement HasAndroidInjector (se non lo fa già). Per esempio, se il tuo Fragmentha bisogno di binding da YourActivitySubcomponent, il tuo codice sarà simile a questo:

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

Perché DispatchingAndroidInjector cerca ilAndroidInjector.Factory appropriato dalla classe in fase di esecuzione, una classe base può implementareHasAndroidInjector così come chiamare AndroidInjection.inject(). Tutto ciò che ogni sottoclasse deve fare è legare un @Subcomponent corrispondente. Dagger fornisce alcuni tipi base che fanno questo, come DaggerActivity e DaggerFragment, se non si ha una gerarchia di classi complicata. Dagger fornisce anche un DaggerApplication per lo stesso scopo – tutto quello che dovete fare è estenderlo e sovrascrivere il metodo applicationInjector() per restituire il componente che dovrebbe iniettare il Application.

Sono inclusi anche i seguenti tipi:

  • DaggerService e DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

Nota: DaggerBroadcastReceiver dovrebbe essere usato solo quando ilBroadcastReceiver è registrato nel AndroidManifest.xml. Quando ilBroadcastReceiver è creato nel tuo codice, preferisci invece l’iniezione del costruttore.

Biblioteche di supporto

Per gli utenti della libreria di supporto Android, i tipi paralleli esistono nel pacchettodagger.android.support.

TODO(ronshapiro): dovremmo iniziare a dividere questo per pacchetti androidx

Come lo prendo?

Aggiungi il seguente al tuo 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 iniettare

L’iniezione del costruttore è preferita quando possibile perché javacgarantisce che nessun campo sia referenziato prima che sia stato impostato, il che aiuta ad evitareNullPointerExceptions. Quando l’iniezione dei membri è richiesta (come discusso sopra), si preferisce iniettare il più presto possibile. Per questo motivo, DaggerActivity richiama AndroidInjection.inject() immediatamente in onCreate(), prima di chiamaresuper.onCreate(), e DaggerFragment fa lo stesso in onAttach(), il che previene anche le incongruenze se il Fragment viene riattaccato.

È cruciale chiamare AndroidInjection.inject() prima di super.onCreate() in un Activity, poiché la chiamata a super attacca Fragment dall’istanza di attività precedente durante il cambio di configurazione, che a sua volta inietta i Fragment. Affinché l’iniezione Fragment abbia successo, il Activitydeve essere già iniettato. Per gli utenti di ErrorProne, è un errore del compilatore chiamare AndroidInjection.inject() dopo super.onCreate().

FAQ

Scoping AndroidInjector.Factory

AndroidInjector.Factory vuole essere un’interfaccia senza stato in modo che gli implementatori non debbano preoccuparsi di gestire lo stato relativo all’oggetto che sarà iniettato. Quando DispatchingAndroidInjector richiede unAndroidInjector.Factory, lo fa attraverso un Provider in modo da non conservare esplicitamente nessuna istanza del factory. Poiché alcune implementazioni possono mantenere un’istanza del Activity/Fragment/etc che viene iniettato, è un errore di compilazione applicare uno scope ai metodi che li forniscono. Se siete sicuri che il vostro AndroidInjector.Factory non mantiene un’istanza dell’oggetto iniettato, potete sopprimere questo errore applicando@SuppressWarnings("dagger.android.ScopedInjectorFactory") al vostro metodo del modulo.