Podstawowe informacje o sztyletach

Ręczne wstrzykiwanie zależności lub lokalizatory usług w aplikacji na Androida może powodować problemy w zależności od rozmiaru projektu. Możesz ograniczyć złożoność projektu podczas skalowania w górę, zarządzając zależnościami za pomocą narzędzia Dagger.

Dagger automatycznie generuje kod, który naśladuje kod, który w innym przypadku zostałby napisany odręcznie. Kod jest generowany podczas kompilacji, więc można go śledzić i jest wydajniejszy niż inne rozwiązania oparte na odczuciach, takie jak Guice.

Zalety korzystania ze sztyletu

Dagger uwalnia Cię od żmudnego i podatnego na błędy kodu. Ta funkcja:

  • Generuję kod AppContainer (wykres aplikacji), który został ręcznie zaimplementowany w sekcji DIU.

  • Tworzę fabryki dla klas dostępnych w wykresie aplikacji. W ten sposób zależności są zaspokajane wewnętrznie.

  • Podejmowanie decyzji o tym, czy ponownie użyć zależności, czy utworzyć nową instancję za pomocą zakresów.

  • Tworzenie kontenerów dla określonych przepływów (tak jak w przypadku logowania w poprzedniej sekcji za pomocą podkomponentów Daggera). Poprawia to wydajność aplikacji, zwalniając obiekty do pamięci, gdy nie są już potrzebne.

Dagger robi to automatycznie podczas kompilacji, o ile zadeklarujesz zależności klasy i określisz, jak mają być spełnione warunki za pomocą adnotacji. Dagger generuje kod podobny do tego, który napisałbyś ręcznie. Dagger tworzy wewnętrznie graf obiektów, do którego może się odwoływać, aby znaleźć sposób udostępnienia instancji klasy. Dla każdej klasy na wykresie Dagger generuje klasę factory-type, której używa wewnętrznie do pobierania instancji tego typu.

W czasie kompilacji Dagger dokładnie opisuje Twój kod i:

  • Tworzy i weryfikuje wykresy zależności, upewniając się, że:

    • Można spełnić zależności każdego obiektu, więc nie ma wyjątków od środowiska wykonawczego.
    • Nie istnieją cykle zależności, nie ma więc nieskończonych pętli.
  • Generuje klasy używane w czasie działania do tworzenia rzeczywistych obiektów i ich zależności.

Prosty przypadek użycia w grze Dagger: generowanie fabryki

Aby pokazać, jak można pracować z Daggerem, utworzymy prostą fabrykę dla klasy UserRepository przedstawionej na tym diagramie:

Określ UserRepository w ten sposób:

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

    ...
}

Dodaj adnotację @Inject do konstruktora UserRepository, aby Dagger wiedział, jak utworzyć 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;
    }
}

W powyższym fragmencie kodu mówisz Daggerowi:

  1. Jak utworzyć instancję UserRepository za pomocą konstruktora z adnotacjami @Inject.

  2. Jakie są jego zależności: UserLocalDataSource i UserRemoteDataSource.

Dagger wie teraz, jak utworzyć instancję UserRepository, ale nie wie, jak utworzyć jej zależności. Jeśli dodasz adnotacje do innych klas, Dagger wie, jak je tworzyć:

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() { }
}

Komponenty sztyletu

Dagger może utworzyć graf zależności w projekcie, którego może użyć, aby znaleźć odpowiednie miejsca docelowe. Aby to umożliwić, musisz utworzyć interfejs i oznaczyć go adnotacjami @Component. Dagger tworzy kontener tak jak w przypadku ręcznego wstrzykiwania zależności.

W interfejsie @Component możesz zdefiniować funkcje, które zwracają instancje potrzebnych klas (np. UserRepository). Pole @Component informuje Dagger, aby wygenerować kontener ze wszystkimi zależnościami wymaganymi do spełnienia podanych przez niego typów. Jest to tzw. komponent sztyletu, który zawiera wykres składający się z obiektów, które Dagger potrafi udostępnić, oraz powiązanych z nimi zależności.

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();
}

Gdy tworzysz projekt, Dagger generuje dla Ciebie implementację interfejsu ApplicationGraph: DaggerApplicationGraph. Dzięki procesorowi adnotacji Dagger tworzy wykres zależności, który składa się ze relacji między 3 klasami (UserRepository, UserLocalDatasource i UserRemoteDataSource) z tylko jednym punktem wejścia: uzyskaniem instancji UserRepository. Można go użyć w następujący sposób:

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 tworzy nową instancję UserRepository za każdym razem, gdy jest potrzebne.

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)

Czasami musisz mieć w kontenerze unikalną instancję zależności. Może to być przydatne z kilku powodów:

  1. Chcesz, aby inne typy, które mają ten typ zależność, współdzieliły tę samą instancję, np. wiele obiektów ViewModel w procesie logowania, które używają tego samego tagu LoginUserData.

  2. Utworzenie obiektu jest kosztowne, a nie chcesz tworzyć nowej instancji za każdym razem, gdy jest on zadeklarowany jako zależność (np. w parzerze JSON).

W tym przykładzie możesz chcieć umieścić na wykresie unikalną instancję UserRepository, aby za każdym razem, gdy poprosisz o wskazanie obiektu UserRepository, zawsze pojawiała się ta sama instancja. Jest to przydatne w tym przykładzie, ponieważ w rzeczywistej aplikacji z bardziej złożonym wykresem może być wiele obiektów ViewModel w zależności od UserRepository i nie chcesz tworzyć nowych wystąpień UserLocalDataSource i UserRemoteDataSource za każdym razem, gdy trzeba podawać parametr UserRepository.

W przypadku ręcznego wstrzykiwania zależności trzeba przekazać tę samą instancję UserRepository do konstruktorów klas ViewModel. Jednak w Daggerze, ponieważ nie pisze się tego kodu ręcznie, musisz dać Daggerowi informację, że chcesz użyć tej samej instancji. Można to zrobić za pomocą adnotacji zakresu.

Określanie zakresu za pomocą sztyletu

Za pomocą adnotacji zakresu możesz ograniczyć czas życia obiektu do czasu życia jego komponentu. Oznacza to, że za każdym razem, gdy trzeba podać ten typ, używana jest ta sama instancja zależności.

Aby uzyskać unikalną instancję UserRepository, gdy prosisz o repozytorium w ApplicationGraph, użyj tej samej adnotacji zakresu dla interfejsu @Component i interfejsu UserRepository. Możesz użyć adnotacji @Singleton, która jest już częścią pakietu javax.inject używanego przez 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;
    }
}

Możesz też utworzyć adnotację dotyczącą zakresu niestandardowego i jej używać. Adnotację dotyczącą zakresu możesz utworzyć w ten sposób:

Kotlin

// Creates MyCustomScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope

Java

// Creates MyCustomScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}

Następnie możesz korzystać z usługi w taki sam sposób jak poprzednio:

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

W obu przypadkach obiekt jest udostępniany z tym samym zakresem, który jest używany w adnotacjach interfejsu @Component. Dzięki temu za każdym razem, gdy wywołujesz usługę applicationGraph.repository(), otrzymujesz to samo wystąpienie 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)

Podsumowanie

Warto znać zalety Daggera i poznać podstawy jego działania, zanim zaczniesz używać go w bardziej złożonych scenariuszach.

Na następnej stronie dowiesz się, jak dodać Dagger do aplikacji na Androida.