A injeção de dependência manual ou os localizadores de serviço em um app Android podem ser problemáticos, dependendo do tamanho do projeto. Você pode limitar a complexidade do seu projeto conforme ele se expande usando o Dagger para gerenciar dependências.
O Dagger gera automaticamente um código que imita o que você teria programado manualmente. Como o código é gerado no momento da compilação, ele é rastreável e tem melhor desempenho que outras soluções baseadas em reflexões, como o Guice.
As vantagens de usar o Dagger
O Dagger libera você do processo tedioso de ter que escrever código boilerplate propenso a erros. É possível fazer o seguinte:
Gerar código
AppContainer
(gráfico do aplicativo) que você implementou manualmente na seção da ID manual.Criar fábricas para as classes disponíveis no gráfico do aplicativo. É assim que as dependências são satisfeitas internamente.
Escolher reutilizar uma dependência ou criar uma nova instância usando escopos.
Criar contêineres para fluxos específicos, como você fez com o fluxo de login na seção anterior usando subcomponentes do Dagger. Isso melhora o desempenho do app ao liberar objetos na memória quando eles não são mais necessários.
O Dagger faz tudo isso automaticamente no tempo de build, contanto que você declare as dependências de uma classe e especifique como elas vão ser satisfeitas usando anotações. O Dagger gera um código semelhante ao que você escreveria manualmente. Internamente, o Dagger cria um gráfico de objetos que ele pode referenciar para encontrar a maneira de fornecer uma instância de uma classe. Para cada classe no gráfico, o Dagger gera uma classe do tipo fábrica que a ferramenta usa internamente para receber instâncias desse tipo.
No tempo de build, o Dagger revisa seu código e:
cria e valida gráficos de dependência, garantindo que:
- as dependências de cada objeto possam ser atendidas, de modo que não haja exceções de tempo de execução.
- não haja ciclos de dependência, portanto, não haja loops infinitos.
Gera as classes usadas no tempo de execução para criar os objetos reais e suas respectivas dependências.
Um caso de uso simples no Dagger: como gerar uma fábrica
Para demonstrar como você pode trabalhar usando o Dagger, vamos criar uma
fábrica simples para a classe UserRepository
mostrada no
diagrama a seguir:
Defina o UserRepository
da seguinte maneira:
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; } ... }
Adicione uma anotação @Inject
ao construtor UserRepository
para que o Dagger saiba
como criar um 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; } }
No snippet de código acima, você está informando ao Dagger:
como criar uma instância
UserRepository
com o construtor anotado@Inject
.quais são as dependências,
UserLocalDataSource
eUserRemoteDataSource
.
Agora, o Dagger sabe criar uma instância de UserRepository
, mas não
sabe como criar as dependências. Se você anotar as outras classes também,
o Dagger saberá como criá-las:
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() { } }
Componentes do Dagger
O Dagger pode criar um gráfico das dependências no seu projeto que pode ser
usado para descobrir onde ele precisará recebê-las quando for necessário.
Para que o Dagger faça isso, é necessário criar uma interface e anotá-la com
@Component
. O Dagger cria um contêiner da mesma forma que você faria com a injeção
manual de dependência.
Dentro da interface @Component
, é possível definir funções que retornam
instâncias das classes necessárias (ou seja, UserRepository
). O @Component
instrui
o Dagger a gerar um contêiner com todas as dependências necessárias para satisfazer os
tipos que ele expõe. Isso é chamado de componente Dagger e contém
um gráfico que consiste nos objetos que o Dagger sabe como
fornecer e nas suas respectivas dependências.
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 você cria o projeto, o Dagger gera uma implementação da
interface ApplicationGraph
para você: DaggerApplicationGraph
. Com o
processador de anotações, o Dagger cria um gráfico de dependência que consiste nas
relações entre as três classes (UserRepository
,
UserLocalDatasource
e UserRemoteDataSource
) com apenas um ponto de entrada:
receber uma instância UserRepository
. Você pode usá-lo da seguinte maneira:
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();
O Dagger cria uma nova instância de UserRepository
sempre que solicitado.
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)
Às vezes, você precisa ter uma instância exclusiva de uma dependência em um contêiner. Você pode querer isso por vários motivos:
Quer que outros tipos que têm esse tipo como dependência compartilhem a mesma instância, como vários objetos
ViewModel
no fluxo de login usando o mesmoLoginUserData
.Um objeto é algo que leva tempo para ser criado, e você não quer ter que criar uma nova instância sempre que for declarada como uma dependência (por exemplo, um analisador JSON).
No exemplo, convém ter uma instância exclusiva de UserRepository
disponível no gráfico para que sempre que solicitar um UserRepository
, você
receba a mesma instância. Isso é útil no seu exemplo porque, em um
aplicativo real com um gráfico de aplicativo mais complexo, você pode ter
vários objetos ViewModel
dependendo de UserRepository
e não quer
ter que criar novas instâncias de UserLocalDataSource
e UserRemoteDataSource
sempre que UserRepository
precisar ser fornecido.
Na injeção manual de dependência, você faz isso passando a mesma
instância de UserRepository
para os construtores das classes ViewModel. Porém,
no Dagger, como você não está escrevendo esse código manualmente, é necessário informar
que você quer usar a mesma instância. Isso pode ser feito usando anotações de
escopo.
Escopo com o Dagger
Você pode usar anotações de escopo para limitar o ciclo de vida de um objeto ao do componente. Isso significa que a mesma instância de uma dependência é usada sempre que esse tipo precisa ser fornecido.
Para ter uma instância exclusiva de um UserRepository
ao solicitar o repositório
em ApplicationGraph
, use a mesma anotação de escopo para a interface @Component
e para UserRepository
. Você pode usar a anotação @Singleton
que
já vem com o pacote javax.inject
que o Dagger usa:
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; } }
Como alternativa, é possível criar e usar uma anotação de escopo personalizado. Você pode criar uma anotação de escopo da seguinte forma:
Kotlin
// Creates MyCustomScope @Scope @MustBeDocumented @Retention(value = AnnotationRetention.RUNTIME) annotation class MyCustomScope
Java
// Creates MyCustomScope @Scope @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomScope {}
Em seguida, você pode continuar como antes:
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; } }
Nos dois casos, o objeto é fornecido com o mesmo escopo usado para anotar a interface
@Component
. Assim, sempre que você chamar
applicationGraph.repository()
, receberá a mesma instância de
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)
Conclusão
É importante estar ciente dos benefícios do Dagger e das noções básicas de como ele funciona antes de usá-lo em cenários mais complicados.
Na próxima página, você vai aprender a adicionar o Dagger a um app Android.