Servizio o inserimento manuale delle dipendenze in un'app per Android possono essere problematici, a seconda delle dimensioni progetto. Puoi limitare la complessità del tuo progetto con lo scale up utilizzando Dagger per gestire le dipendenze.
Dagger genera automaticamente un codice che imita quello che altrimenti sono scritti a mano. Poiché il codice viene generato al momento della compilazione, è tracciabile e più performanti di altre soluzioni basate sulla riflessione, come Guice.
Vantaggi dell'utilizzo di Dagger
Dagger ti evita di scrivere codice boilerplate noioso e soggetto a errori:
Generazione del codice
AppContainer
(grafico dell'applicazione) manuale implementato nella sezione delle distribuzioni manuali.Creazione di fabbriche per le classi disponibili nel grafico dell'applicazione. Questo è il modo in cui le dipendenze vengono soddisfatte internamente.
Decidere se riutilizzare una dipendenza o creare una nuova istanza tramite l'utilizzo degli ambiti.
Creare container per flussi specifici come hai fatto per il flusso di accesso nella sezione precedente utilizzando i sottocomponenti di Dagger. In questo modo migliorino rilasciando oggetti in memoria quando non sono più necessari.
Dagger fa automaticamente tutto questo in fase di creazione, purché dichiarare le dipendenze di una classe e specificare come soddisfarle usando annotazioni. Dagger genera un codice simile a quello che avresti scritto manualmente. Internamente, Dagger crea un grafo di oggetti a cui può fare riferimento per trovare il modo di fornire un'istanza di una classe. Per ogni corso nel grafico, Dagger genera una classe factory-type che utilizza internamente per ottenere istanze di quel tipo.
In fase di creazione, Dagger illustra il tuo codice e:
Crea e convalida i grafici delle dipendenze, garantendo che:
- Le dipendenze di ogni oggetto possono essere soddisfatte, quindi non è previsto eccezioni.
- Non esistono cicli di dipendenza, quindi non esistono loop infiniti.
Genera le classi utilizzate in fase di runtime per creare gli oggetti effettivi e le loro dipendenze.
Un caso d'uso semplice in Dagger: generare una fabbrica
Per dimostrare come puoi lavorare con Dagger, creiamo un semplice
factory per la classe UserRepository
mostrata in
diagramma seguente:
Definisci UserRepository
come segue:
Kotlin
class UserRepository( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } ... }
Aggiungi un'annotazione @Inject
al costruttore UserRepository
in modo che Dagger sappia
come creare un UserRepository
:
Kotlin
// @Inject lets Dagger know how to create instances of this object class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; // @Inject lets Dagger know how to create instances of this object @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
Nello snippet di codice riportato sopra, stai dicendo a Dagger:
Come creare un'istanza
UserRepository
con@Inject
annotato come costruttore.Quali sono le sue dipendenze:
UserLocalDataSource
eUserRemoteDataSource
.
Ora Dagger sa come creare un'istanza di UserRepository
, ma
a creare le sue dipendenze. Se aggiungi annotazioni anche agli altri corsi,
Dagger sa come crearli:
Kotlin
// @Inject lets Dagger know how to create instances of these objects class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... }
Java
public class UserLocalDataSource { @Inject public UserLocalDataSource() { } } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } }
Componenti di pugnale
Dagger può creare un grafico delle dipendenze nel progetto
per scoprire dove dovrebbe ottenere queste dipendenze quando sono necessarie.
Per farlo, devi creare un'interfaccia e annotarla con
@Component
. Dagger crea un container come faresti con un metodo
l'inserimento delle dipendenze.
All'interno dell'interfaccia @Component
, puoi definire funzioni che restituiscono
le istanze delle classi che ti servono (ad es. UserRepository
). @Component
dice
Dagger per generare un container con tutte le dipendenze necessarie per soddisfare
tipi che espone. Questo è chiamato componente Dagger. contiene
un grafo composto da oggetti che Dagger sa
e le rispettive dipendenze.
Kotlin
// @Component makes Dagger create a graph of dependencies @Component interface ApplicationGraph { // The return type of functions inside the component interface is // what can be provided from the container fun repository(): UserRepository }
Java
// @Component makes Dagger create a graph of dependencies @Component public interface ApplicationGraph { // The return type of functions inside the component interface is // what can be consumed from the graph UserRepository userRepository(); }
Quando crei il progetto, Dagger genera un'implementazione
Interfaccia ApplicationGraph
per te: DaggerApplicationGraph
. Con le sue
processore di annotazione, Dagger crea un grafico delle dipendenze composto
relazioni tra le tre classi (UserRepository
,
UserLocalDatasource
e UserRemoteDataSource
) con un solo punto di ingresso:
ottenere un'istanza UserRepository
. Puoi utilizzarlo nel seguente modo:
Kotlin
// Create an instance of the application graph val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() // Grab an instance of UserRepository from the application graph val userRepository: UserRepository = applicationGraph.repository()
Java
// Create an instance of the application graph ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); // Grab an instance of UserRepository from the application graph UserRepository userRepository = applicationGraph.userRepository();
Dagger crea una nuova istanza di UserRepository
ogni volta che viene richiesta.
Kotlin
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository != userRepository2)
Java
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); UserRepository userRepository = applicationGraph.userRepository(); UserRepository userRepository2 = applicationGraph.userRepository(); assert(userRepository != userRepository2)
A volte è necessario disporre di un'istanza univoca di una dipendenza in un container. I motivi potrebbero essere diversi:
Vuoi che gli altri tipi che hanno questo tipo come dipendenza condividano lo stesso ad esempio più oggetti
ViewModel
nel flusso di accesso utilizzando lo stessoLoginUserData
.Un oggetto è costoso da creare e non si vuole crearne uno nuovo un'istanza ogni volta che viene dichiarata come dipendenza (ad esempio, un parser JSON).
Nell'esempio, potresti voler avere un'istanza univoca di UserRepository
disponibili nel grafico; in questo modo, ogni volta che chiedi un UserRepository
,
otteniamo sempre la stessa istanza. Questo è utile nel tuo esempio perché in una
un'applicazione reale con un grafico applicativo più complesso, potresti
più oggetti ViewModel
a seconda di UserRepository
e non vuoi
per creare nuove istanze di UserLocalDataSource
e UserRemoteDataSource
ogni volta che serve UserRepository
.
Nell'inserimento manuale delle dipendenze, occorre passare lo stesso
istanza di UserRepository
ai costruttori delle classi ViewModel; ma
in Dagger, dato che non stai scrivendo il codice manualmente, devi lasciare
Dagger sa che vuoi usare la stessa istanza. Ciò può essere fatto utilizzando l'ambito
annotazioni.
Definizione dell'ambito con obelisco
Puoi utilizzare le annotazioni dell'ambito per limitare la durata di un oggetto alla durata del suo componente. Ciò significa che viene utilizzata la stessa istanza di una dipendenza ogni volta che è necessario specificare quel tipo.
Avere un'istanza univoca di UserRepository
quando richiedi il repository
in ApplicationGraph
, utilizza la stessa annotazione di ambito per @Component
e UserRepository
. Puoi utilizzare l'annotazione @Singleton
che
viene già fornito con il pacchetto javax.inject
utilizzato da Dagger:
Kotlin
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are bound to the life of the graph and so // the same instance of that type is provided every time the type is requested. @Singleton @Component interface ApplicationGraph { fun repository(): UserRepository } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are scoped to the graph and the same // instance of that type is provided every time the type is requested. @Singleton @Component public interface ApplicationGraph { UserRepository userRepository(); } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @Singleton public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
In alternativa, puoi creare e utilizzare un'annotazione con ambito personalizzato. Puoi creare un'annotazione relativa all'ambito nel seguente modo:
Kotlin
// Creates MyCustomScope @Scope @MustBeDocumented @Retention(value = AnnotationRetention.RUNTIME) annotation class MyCustomScope
Java
// Creates MyCustomScope @Scope @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomScope {}
Poi potrai utilizzarlo come prima:
Kotlin
@MyCustomScope @Component interface ApplicationGraph { fun repository(): UserRepository } @MyCustomScope class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val service: UserService ) { ... }
Java
@MyCustomScope @Component public interface ApplicationGraph { UserRepository userRepository(); } @MyCustomScope public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
In entrambi i casi, all'oggetto viene fornito lo stesso ambito utilizzato per annotare
interfaccia di @Component
. Ogni volta che chiami
applicationGraph.repository()
, ottieni la stessa istanza
UserRepository
.
Kotlin
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository == userRepository2)
Java
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); UserRepository userRepository = applicationGraph.userRepository(); UserRepository userRepository2 = applicationGraph.userRepository(); assert(userRepository == userRepository2)
Conclusione
È importante conoscere i vantaggi di Dagger e le nozioni di base sul suo funzionamento. prima di poterla usare in scenari più complicati.
Nella pagina successiva, scoprirai come aggiungere Dagger a un'applicazione Android.