Iniezione di dipendenze con Hilt

Hilt è una libreria di inserimento delle dipendenze per Android che riduce il boilerplate dell'inserimento manuale delle dipendenze nel tuo progetto. L'inserimento manuale delle dipendenze richiede di costruire manualmente ogni classe e le relative dipendenze e di utilizzare i contenitori per riutilizzare e gestire le dipendenze.

Hilt fornisce un modo standard per utilizzare l'inserimento delle dipendenze nella tua applicazione fornendo contenitori per ogni classe Android nel tuo progetto e gestendone automaticamente i cicli di vita. Hilt è basato sulla popolare libreria DI Dagger per sfruttare la correttezza in fase di compilazione, le prestazioni di runtime, la scalabilità e il supporto di Android Studio forniti da Dagger. Per ulteriori informazioni, consulta Hilt e Dagger.

Questa guida spiega i concetti di base di Hilt e dei relativi contenitori generati. Include anche una dimostrazione di come eseguire il bootstrap di un'app esistente per utilizzare Hilt.

Aggiunta di dipendenze

Innanzitutto, aggiungi il plug-in hilt-android-gradle-plugin al file build.gradle principale del progetto:

Groovy

plugins {
  ...
  id 'com.google.dagger.hilt.android' version '2.56.2' apply false
}

Kotlin

plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.56.2" apply false
}

Dopodiché, applica il plug-in Gradle e aggiungi queste dipendenze nel file app/build.gradle:

Groovy

...
plugins {
  id 'com.google.devtools.ksp'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

dependencies {
  implementation "com.google.dagger:hilt-android:2.56.2"
  ksp "com.google.dagger:hilt-compiler:2.56.2"
}

Kotlin

plugins {
  id("com.google.devtools.ksp")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.56.2")
  ksp("com.google.dagger:hilt-android-compiler:2.56.2")
}

Hilt utilizza le funzionalità di Java 8. Per abilitare Java 8 nel tuo progetto, aggiungi quanto segue al file app/build.gradle:

Groovy

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

Kotlin

android {
  ...
  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
  }
}

Classe di applicazione Hilt

Tutte le app che utilizzano Hilt devono contenere una classe Application annotata con @HiltAndroidApp.

@HiltAndroidApp attiva la generazione di codice di Hilt, inclusa una classe base per la tua applicazione che funge da contenitore delle dipendenze a livello di applicazione.

Kotlin

@HiltAndroidApp
class ExampleApplication : Application() { ... }

Java

@HiltAndroidApp
public class ExampleApplication extends Application { ... }

Questo componente Hilt generato è collegato al ciclo di vita dell'oggetto Application e fornisce le dipendenze. Inoltre, è il componente principale dell'app, il che significa che gli altri componenti possono accedere alle dipendenze che fornisce.

Inserire dipendenze nelle classi Android

Una volta configurato Hilt nella classe Application e disponibile un componente a livello di applicazione, Hilt può fornire dipendenze ad altre classi Android con l'annotazione @AndroidEntryPoint:

Kotlin

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }

Java

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }

Al momento Hilt supporta le seguenti classi Android:

  • Application (utilizzando @HiltAndroidApp)
  • ViewModel (utilizzando @HiltViewModel)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

Se annoti una classe Android con @AndroidEntryPoint, devi anche annotare le classi Android che dipendono da essa. Ad esempio, se annoti un frammento, devi annotare anche le attività in cui lo utilizzi.

@AndroidEntryPoint genera un singolo componente Hilt per ogni classe Android nel tuo progetto. Questi componenti possono ricevere dipendenze dalle rispettive classi padre, come descritto in Gerarchia dei componenti.

Per ottenere le dipendenze da un componente, utilizza l'annotazione @Inject per eseguire l'inserimento di campi:

Kotlin

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Java

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

Le classi inserite da Hilt possono avere altre classi base che utilizzano anche l'inserimento. Queste classi non richiedono l'annotazione @AndroidEntryPoint se sono astratte.

Per scoprire di più sul callback del ciclo di vita in cui viene inserita una classe Android, consulta Durata dei componenti.

Definisci le associazioni Hilt

Per eseguire l'inserimento di campi, Hilt deve sapere come fornire istanze delle dipendenze necessarie dal componente corrispondente. Un binding contiene le informazioni necessarie per fornire istanze di un tipo come dipendenza.

Un modo per fornire informazioni di binding a Hilt è l'iniezione del costruttore. Utilizza l'annotazione @Inject sul costruttore di una classe per indicare a Hilt come fornire istanze di quella classe:

Kotlin

class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

Java

public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

I parametri di un costruttore annotato di una classe sono le dipendenze di quella classe. Nell'esempio, AnalyticsAdapter ha AnalyticsService come dipendenza. Pertanto, Hilt deve anche sapere come fornire istanze di AnalyticsService.

Moduli Hilt

A volte un tipo non può essere inserito nel costruttore. Questo può accadere per diversi motivi. Ad esempio, non puoi inserire un'interfaccia nel costruttore. Inoltre, non puoi inserire un tipo nel costruttore che non ti appartiene, ad esempio una classe di una libreria esterna. In questi casi, puoi fornire a Hilt le informazioni di binding utilizzando i moduli Hilt.

Un modulo Hilt è una classe annotata con @Module. Come un modulo Dagger, indica a Hilt come fornire istanze di determinati tipi. A differenza dei moduli Dagger, devi annotare i moduli Hilt con @InstallIn per indicare a Hilt in quale classe Android verrà utilizzato o installato ogni modulo.

Le dipendenze che fornisci nei moduli Hilt sono disponibili in tutti i componenti generati associati alla classe Android in cui installi il modulo Hilt.

Inserire istanze di interfacce con @Binds

Considera l'esempio AnalyticsService. Se AnalyticsService è un'interfaccia, non puoi eseguirne l'iniezione nel costruttore. Fornisci invece a Hilt le informazioni di binding creando una funzione astratta annotata con @Binds all'interno di un modulo Hilt.

L'annotazione @Binds indica a Hilt quale implementazione utilizzare quando deve fornire un'istanza di un'interfaccia.

La funzione annotata fornisce le seguenti informazioni a Hilt:

  • Il tipo restituito dalla funzione indica a Hilt di quale interfaccia la funzione fornisce istanze.
  • Il parametro della funzione indica a Hilt quale implementazione fornire.

Kotlin

interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Java

public interface AnalyticsService {
  void analyticsMethods();
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
public class AnalyticsServiceImpl implements AnalyticsService {
  ...
  @Inject
  AnalyticsServiceImpl(...) {
    ...
  }
}

@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {

  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

Il modulo Hilt AnalyticsModule è annotato con @InstallIn(ActivityComponent.class) perché vuoi che Hilt inserisca questa dipendenza in ExampleActivity. Questa annotazione indica che tutte le dipendenze in AnalyticsModule sono disponibili in tutte le attività dell'app.

Inserire istanze con @Provides

Le interfacce non sono l'unico caso in cui non è possibile inserire un tipo nel costruttore. L'inserimento del costruttore non è possibile anche se non possiedi la classe perché proviene da una libreria esterna (classi come Retrofit, OkHttpClient, o database Room) o se le istanze devono essere create con il pattern builder.

Considera l'esempio precedente. Se non possiedi direttamente la classe AnalyticsService, puoi indicare a Hilt come fornire istanze di questo tipo creando una funzione all'interno di un modulo Hilt e annotandola con @Provides.

La funzione annotata fornisce le seguenti informazioni a Hilt:

  • Il tipo restituito dalla funzione indica a Hilt il tipo di istanze fornite dalla funzione.
  • I parametri della funzione indicano a Hilt le dipendenze del tipo corrispondente.
  • Il corpo della funzione indica a Hilt come fornire un'istanza del tipo corrispondente. Hilt esegue il corpo della funzione ogni volta che deve fornire un'istanza di quel tipo.

Kotlin

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Java

@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    // Potential dependencies of this type
  ) {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

Fornire più binding per lo stesso tipo

Nei casi in cui è necessario che Hilt fornisca implementazioni diverse dello stesso tipo come dipendenze, devi fornire a Hilt più binding. Puoi definire più binding per lo stesso tipo con i qualificatori.

Un qualificatore è un'annotazione che utilizzi per identificare un binding specifico per un tipo quando questo tipo ha più binding definiti.

Considera l'esempio. Se devi intercettare le chiamate a AnalyticsService, puoi utilizzare un oggetto OkHttpClient con un intercettore. Per altri servizi, potresti dover intercettare le chiamate in modo diverso. In questo caso, devi indicare a Hilt come fornire due implementazioni diverse di OkHttpClient.

Innanzitutto, definisci i qualificatori che utilizzerai per annotare i metodi @Binds o @Provides:

Kotlin

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient

Java

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}

Poi, Hilt deve sapere come fornire un'istanza del tipo corrispondente a ogni qualificatore. In questo caso, potresti utilizzare un modulo Hilt con @Provides. Entrambi i metodi hanno lo stesso tipo restituito, ma i qualificatori li etichettano come due binding diversi:

Kotlin

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}

Java

@Module
@InstallIn(ActivityComponent.class)
public class NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideAuthInterceptorOkHttpClient(
    AuthInterceptor authInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(authInterceptor)
                   .build();
  }

  @OtherInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideOtherInterceptorOkHttpClient(
    OtherInterceptor otherInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(otherInterceptor)
                   .build();
  }
}

Puoi inserire il tipo specifico di cui hai bisogno annotando il campo o il parametro con il qualificatore corrispondente:

Kotlin

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}

Java

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    @AuthInterceptorOkHttpClient OkHttpClient okHttpClient
  ) {
      return new Retrofit.Builder()
                  .baseUrl("https://example.com")
                  .client(okHttpClient)
                  .build()
                  .create(AnalyticsService.class);
  }
}

// As a dependency of a constructor-injected class.
public class ExampleServiceImpl ... {

  private final OkHttpClient okHttpClient;

  @Inject
  ExampleServiceImpl(@AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
    this.okHttpClient = okHttpClient;
  }
}

// At field injection.
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @AuthInterceptorOkHttpClient
  @Inject
  OkHttpClient okHttpClient;
  ...
}

Come best practice, se aggiungi un qualificatore a un tipo, aggiungi qualificatori a tutti i modi possibili per fornire questa dipendenza. Lasciare l'implementazione di base o comune senza un qualificatore è soggetto a errori e potrebbe comportare l'inserimento della dipendenza errata da parte di Hilt.

Qualificatori predefiniti in Hilt

Hilt fornisce alcuni qualificatori predefiniti. Ad esempio, poiché potresti aver bisogno della classe Context dall'applicazione o dall'attività, Hilt fornisce i qualificatori @ApplicationContext e @ActivityContext.

Supponiamo che la classe AnalyticsAdapter dell'esempio richieda il contesto dell'attività. Il seguente codice mostra come fornire il contesto dell'attività a AnalyticsAdapter:

Kotlin

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }

Java

public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(
    @ActivityContext Context context,
    AnalyticsService service
  ) {
    this.context = context;
    this.service = service;
  }
}

Per altri binding predefiniti disponibili in Hilt, consulta Binding predefiniti dei componenti.

Componenti generati per le classi Android

Per ogni classe Android in cui puoi eseguire l'inserimento di campi, esiste un componente Hilt associato a cui puoi fare riferimento nell'annotazione @InstallIn. Ogni componente Hilt è responsabile dell'inserimento dei relativi binding nella classe Android corrispondente.

Gli esempi precedenti hanno mostrato l'utilizzo di ActivityComponent nei moduli Hilt.

Hilt fornisce i seguenti componenti:

Componente Hilt Iniettore per
SingletonComponent Application
ActivityRetainedComponent N/D
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View ha aggiunto un'annotazione con @WithFragmentBindings
ServiceComponent Service

Durata dei componenti

Hilt crea e distrugge automaticamente le istanze delle classi dei componenti generati seguendo il ciclo di vita delle classi Android corrispondenti.

Componente generato Ora di creazione: Eliminato alle
SingletonComponent Application#onCreate() Application distrutto
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel creato ViewModel distrutto
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() View distrutto
ViewWithFragmentComponent View#super() View distrutto
ServiceComponent Service#onCreate() Service#onDestroy()

Ambiti dei componenti

Per impostazione predefinita, tutti i binding in Hilt sono senza ambito. Ciò significa che ogni volta che la tua app richiede l'associazione, Hilt crea una nuova istanza del tipo necessario.

Nell'esempio, ogni volta che Hilt fornisce AnalyticsAdapter come dipendenza a un altro tipo o tramite l'inserimento di campi (come in ExampleActivity), Hilt fornisce una nuova istanza di AnalyticsAdapter.

Tuttavia, Hilt consente anche di definire l'ambito di un binding per un componente specifico. Hilt crea un binding con ambito una sola volta per istanza del componente a cui è assegnato l'ambito del binding e tutte le richieste per quel binding condividono la stessa istanza.

La tabella seguente elenca le annotazioni dell'ambito per ogni componente generato:

Classe Android Componente generato Ambito
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View ha aggiunto un'annotazione con @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

Nell'esempio, se limiti l'ambito di AnalyticsAdapter a ActivityComponent utilizzando @ActivityScoped, Hilt fornisce la stessa istanza di AnalyticsAdapter per tutta la durata dell'attività corrispondente:

Kotlin

@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

Java

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

Supponiamo che AnalyticsService abbia uno stato interno che richiede l'utilizzo della stessa istanza ogni volta, non solo in ExampleActivity, ma ovunque nell'app. In questo caso, è opportuno definire l'ambito di AnalyticsService in base a SingletonComponent. Il risultato è che ogni volta che il componente deve fornire un'istanza di AnalyticsService, fornisce la stessa istanza ogni volta.

L'esempio seguente mostra come limitare un binding a un componente in un modulo Hilt. L'ambito di un binding deve corrispondere all'ambito del componente in cui è installato, quindi in questo esempio devi installare AnalyticsService in SingletonComponent anziché in ActivityComponent:

Kotlin

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Java

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent.class)
public class AnalyticsModule {

  @Singleton
  @Provides
  public static AnalyticsService provideAnalyticsService() {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

Per scoprire di più sugli ambiti dei componenti Hilt, consulta Ambiti in Android e Hilt.

Gerarchia dei componenti

L'installazione di un modulo in un componente consente di accedere ai relativi binding come dipendenza di altri binding in quel componente o in qualsiasi componente secondario sottostante nella gerarchia dei componenti:

ViewWithFragmentComponent si trova in FragmentComponent. FragmentComponent
    e ViewComponent si trovano in ActivityComponent. ActivityComponent è sotto
    ActivityRetainedComponent. ViewModelComponent si trova in
    ActivityRetainedComponent. ActivityRetainedComponent e ServiceComponent
    si trovano in SingletonComponent.
Figura 1. Gerarchia dei componenti generati da Hilt.

Associazioni predefinite dei componenti

Ogni componente Hilt viene fornito con un insieme di binding predefiniti che Hilt può inserire come dipendenze nei tuoi binding personalizzati. Tieni presente che questi binding corrispondono ai tipi di attività e frammenti generali e non a una sottoclasse specifica. Questo perché Hilt utilizza una singola definizione di componente attività per inserire tutte le attività. Ogni attività ha un'istanza diversa di questo componente.

Componente Android Binding predefiniti
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent Application, Activity
FragmentComponent Application, Activity e Fragment
ViewComponent Application, Activity e View
ViewWithFragmentComponent Application, Activity, Fragment, View
ServiceComponent Application, Service

Il binding del contesto dell'applicazione è disponibile anche utilizzando @ApplicationContext. Ad esempio:

Kotlin

class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }

Java

public class AnalyticsServiceImpl implements AnalyticsService {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ApplicationContext Context context) {
    this.context = context;
  }
}

// The Application binding is available without qualifiers.
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Application application;

  @Inject
  AnalyticsAdapter(Application application) {
    this.application = application;
  }
}

Il binding del contesto dell'attività è disponibile anche utilizzando @ActivityContext. Per esempio:

Kotlin

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) { ... }

Java

public class AnalyticsAdapter {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ActivityContext Context context) {
    this.context = context;
  }
}

// The Activity binding is available without qualifiers.
public class AnalyticsAdapter {

  private final FragmentActivity activity;

  @Inject
  AnalyticsAdapter(FragmentActivity activity) {
    this.activity = activity;
  }
}

Inserire dipendenze in classi non supportate da Hilt

Hilt supporta le classi Android più comuni. Tuttavia, potresti dover eseguire l'inserimento di campi in classi non supportate da Hilt.

In questi casi, puoi creare un punto di ingresso utilizzando l'annotazione @EntryPoint. Un punto di ingresso è il limite tra il codice gestito da Hilt e il codice non gestito. È il punto in cui il codice entra per la prima volta nel grafico degli oggetti gestiti da Hilt. I punti di ingresso consentono a Hilt di utilizzare codice che Hilt non gestisce per fornire dipendenze all'interno del grafico delle dipendenze.

Ad esempio, Hilt non supporta direttamente i content provider. Se vuoi che un fornitore di contenuti utilizzi Hilt per ottenere alcune dipendenze, devi definire un'interfaccia annotata con @EntryPoint per ogni tipo di binding che vuoi e includere i qualificatori. Poi aggiungi @InstallIn per specificare il componente in cui installare il punto di accesso come segue:

Kotlin

class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }

  ...
}

Java

public class ExampleContentProvider extends ContentProvider {

  @EntryPoint
  @InstallIn(SingletonComponent.class)
  interface ExampleContentProviderEntryPoint {
    public AnalyticsService analyticsService();
  }
  ...
}

Per accedere a un punto di ingresso, utilizza il metodo statico appropriato di EntryPointAccessors. Il parametro deve essere l'istanza del componente o l'oggetto @AndroidEntryPoint che funge da contenitore del componente. Assicurati che il componente che trasmetti come parametro e il metodo statico EntryPointAccessors corrispondano alla classe Android nell'annotazione @InstallIn sull'interfaccia @EntryPoint:

Kotlin

class ExampleContentProvider: ContentProvider() {
    ...

  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}

Java

public class ExampleContentProvider extends ContentProvider {

  @Override
  public Cursor query(...) {
    Context appContext = getContext().getApplicationContext();
    ExampleContentProviderEntryPoint hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint.class);
    AnalyticsService analyticsService = hiltEntryPoint.analyticsService();
  }
}

In questo esempio, devi utilizzare ApplicationContext per recuperare il punto di ingresso perché è installato in SingletonComponent. Se l'associazione che volevi recuperare si trovava in ActivityComponent, devi utilizzare ActivityContext.

Hilt and Dagger

Hilt si basa sulla libreria di iniezione delle dipendenze Dagger, fornendo un modo standard per incorporare Dagger in un'applicazione Android.

Per quanto riguarda Dagger, gli obiettivi di Hilt sono i seguenti:

  • Per semplificare l'infrastruttura correlata a Dagger per le app per Android.
  • Per creare un insieme standard di componenti e ambiti per semplificare la configurazione, la leggibilità e la condivisione del codice tra le app.
  • Per fornire un modo semplice per eseguire il provisioning di diversi binding per vari tipi di build, come test, debug o release.

Poiché il sistema operativo Android crea l'istanza di molte delle proprie classi framework, l'utilizzo di Dagger in un'app Android richiede di scrivere una notevole quantità di codice boilerplate. Hilt riduce il codice boilerplate coinvolto nell'utilizzo di Dagger in un'applicazione Android. Hilt genera e fornisce automaticamente quanto segue:

  • Componenti per l'integrazione delle classi del framework Android con Dagger che altrimenti dovresti creare manualmente.
  • Annotazioni di ambito da utilizzare con i componenti generati automaticamente da Hilt.
  • Binding predefiniti per rappresentare le classi Android come Application o Activity.
  • Qualificatori predefiniti per rappresentare @ApplicationContext e @ActivityContext.

Il codice Dagger e Hilt può coesistere nello stesso codebase. Tuttavia, nella maggior parte dei casi è consigliabile utilizzare Hilt per gestire tutto l'utilizzo di Dagger su Android. Per eseguire la migrazione di un progetto che utilizza Dagger a Hilt, consulta la guida alla migrazione e il codelab Eseguire la migrazione dell'app Dagger a Hilt.

Risorse aggiuntive

Per saperne di più su Hilt, consulta le seguenti risorse aggiuntive.

Campioni

Codelab

Blog