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:
Jak utworzyć instancję
UserRepository
za pomocą konstruktora z adnotacjami@Inject
.Jakie są jego zależności:
UserLocalDataSource
iUserRemoteDataSource
.
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:
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 taguLoginUserData
.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.