Nozioni di base sul pugnale

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:

  1. Come creare un'istanza UserRepository con @Inject annotato come costruttore.

  2. Quali sono le sue dipendenze: UserLocalDataSource e UserRemoteDataSource.

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:

  1. 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 stesso LoginUserData.

  2. 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.