Você está na página 1de 25

26/10/2022 21:42 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Capítulo 4 : Gerenciando
dependências em aplicativos Android

Neste capítulo, analisaremos o conceito de injeção de dependência ( DI ) e os


benefícios que ele oferece e veremos como isso era feito no passado em aplica-
tivos Android, seja por injeção manual ou usando o Dagger 2. Examinaremos
algumas das bibliotecas usado em aplicativos Android, parando e analisando
mais detalhadamente a biblioteca Hilt e como ela simplifica a DI para um apli-
cativo Android.

Neste capítulo, abordaremos os seguintes tópicos:

Introdução ao DI
Usando o Dagger 2 para gerenciar dependências
Usando Hilt para gerenciar dependências

Ao final deste capítulo, você estará familiarizado com o padrão DI e bibliote-


cas como Dagger e Hilt, que podem ser usadas para gerenciar dependências
em aplicativos Android.

Requerimentos técnicos

Os requisitos de hardware e software são os seguintes:

Android Studio Arctic Fox 2020.3.1 Patch 3

Os arquivos de código para este capítulo podem ser encontrados aqui:


https://github.com/PacktPublishing/Clean-Android-
Architecture/tree/main/Chapter4 .

Confira o vídeo a seguir para ver o Código em Ação: https://bit.ly/38yFDHz

Introdução ao DI

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 1/25
26/10/2022 21:42 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Nesta seção, veremos o que é a DI, os benefícios que ela oferece e como esse
conceito é aplicado a umAplicativo Android. Em seguida, veremos algumas bi-
bliotecas de DI e como elas funcionam.

Quando uma classe depende da funcionalidade de outra classe, uma depen-


dência é criada entre as duas classes. Para invocar a funcionalidade na classe
da qual você depende, você precisará instanciá-la, como no exemplo a seguir:

class ClasseA(){
    valor privado b: ClassB = ClassB()
    divertido executeA() {
        b.executeB()
    }
}
classe ClasseB(){
    divertido executeB() {
        
    }
}
Neste exemplo, ClassA cria uma nova instância de ClassB e, em seguida,
quando executeA é invocado, ele invocará executeB . Isso representa um pro-
blema porque ClassA terá a responsabilidade extra de criar ClassB . Vamos ver
o que acontece se ClassB precisar mudar para algo como o seguinte:

class ClassB(private val myFlag: Boolean) {


    
    divertido executeB() {
        if (myFlag) {
            // Faça alguma coisa
        } senão {
            //Faça outra coisa
        }
    }
}
Aqui, adicionamos a variável myFlag a ClassB , que é usada no método execu‐
teB . estemudança causaria um erro de compilação porque agora a ClassA pre-

cisará ser modificada para fazer o código compilar.

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 2/25
26/10/2022 21:42 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

class ClasseA(){
    valor privado b: ClassB = ClassB(true)
    divertido executeA() {
        b.executeB()
    }
}
Aqui, precisaremos fornecer um valor booleano quando criarmos ClassB .

Fazer esses tipos de alterações em um aplicativo à medida que sua base de có-
digo aumenta dificultará a manutenção. Uma solução para esse problema é se-
parar como usamos dependências e como as criamos e delegar a criação a um
objeto diferente. Continuando com o exemplo anterior, podemos reescrever
ClassA da seguinte forma:

class ClassA( private val b: ClassB ) {


    divertido executeA() {
        b.executeB()
    }
}
Aqui, removemos a instanciação de ClassB e movemos a variável no constru-
tor de ClassA . Agora, podemos criar uma classe que será responsável por criar
as instâncias de ambas as classes que se parece com o seguinte:

class Injetor() {
    divertido criarA(b: ClasseB) = ClasseA(b)
    fun createB() = ClassB(true)
}
Aqui, temos uma nova classe que criará uma instância de ClassA com ClassB
como parâmetro e um método separado para criar uma instância de ClassB .
Idealmente, quando o programafor inicializado, precisaríamos inicializar to-
das as dependências e passá-las adequadamente:

fun main(args : Array<>) {


    val injetor = Injetor()
    val b = injector.createB()
    val a = injector.createA(b)
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 3/25
26/10/2022 21:42 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Aqui, criamos Injector , que é responsável por criar nossas instâncias e, em


seguida, invocamos os métodos apropriados em Injector para recuperar as
instâncias apropriadas de cada classe. O que fizemos aqui chama-se DI. Em
vez de ClassA criar a instância de ClassB , eleter uma instância de ClassB inje-
tada por meio do construtor, também conhecido como injeção de construtor .

Em ClassB , temos uma instrução if-else no método executeB . Podemos intro-


duzir umabstração lá, então dividimos a instrução if-else em duas implemen-
tações separadas:

class ClassA(privado val b: ClassB) {


    divertido executeA() {
        b.executeB()
    }
}
interface Classe B {
    divertido executeB()
}
class ClassB1() : ClassB {
    substituir fun executeB() {
        // Faça alguma coisa
    }
}
class ClassB2() : ClassB {
    substituir fun executeB() {
        //Faça outra coisa
    }
}
Aqui, ClassA permanece a mesma e ClassB tornou-se uma interface com duas
implementações, chamadas ClassB1 e ClassB2 , representando as implementa-
ções da ramificação if-else . Aqui, podemos usar a classe Injector também
para injetar uma das duas implementações sem exigir nenhuma alteração na
ClassA :

class Injetor() {
    divertido criarA(b: ClasseB) = ClasseA(b)
    divertido createB() = ClassB1()

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 4/25
26/10/2022 21:42 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

}
No método createB , retornamos uma instância de ClassB1 , que será posterior-
mente injetada em ClassA . Isso representa mais um benefício da DI, onde po-
demos fazer com que nosso código dependa deabstrações ao invés de concre-
ções e fornecem concreções diferentes para propósitos diferentes. Com base
nisso, podemos definir os seguintes papéis quando se trata de DI:

Service : Representa o objeto que contém funcionalidade útil ( ClassB1 e


ClassB2 em nosso exemplo)

Interface : Representa a abstração do serviço ( ClassB em nosso exemplo)


Client : Representa o objeto que depende do serviço ( ClassA em nosso
exemplo)
Injector : Representa o objeto responsável por construir os serviços e in-
jetá-los no cliente ( Injector em nosso exemplo)

Figura 4.1 – Diagrama de classe DI

A figura anterior mostra o diagrama de classes do nosso exemplo e o padrão


DI. Podemos observar como a classe Injector é responsável por criar e injetar
as dependências, ClassA é o clienterecebendouma dependência para ClassB ,
que é a interface, e ClassB1 e ClassB2 representam os serviços.

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 5/25
26/10/2022 21:42 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Existem várias classificações dos tipos de DI, e elas giram principalmente em


torno de duas maneiras de injetar dependências:

Injeção de construtor : Ondedependências são passadas pelo construtor.


Injeção de campo : Ondedependências são passadas através de métodos
setter ou alterando as variáveis ​de instância. Isso também pode ser cha-
mado de injeção de setter e podetambém ser expandido para injeção de
interface em que osetter é abstraído para uma interface.

Outro benefício do DI é o fato de tornar o código mais testável. Quando as de-


pendências sãoinjetado em um objeto, torna a classe mais fácil de testar, pois
no código de teste, podemos injetar objetos que nos permitem imitar diversos
comportamentos, chamados de mocks .

Nesta seção, apresentamos o padrão DI, como ele funciona e os problemas que
está resolvendo. Os desenvolvedores podem gerenciar as dependências e a in-
jeção de um aplicativo manualmente, configurando injetores. Mas à medida
que um aplicativo cresce, torna-se difícil mantê-lo, especialmente quandoque-
remos que certos objetos vivam apenas enquanto outros objetos e não en-
quanto o aplicativo, ou manipule diferentes instâncias da mesma classe. Exis-
tem vários frameworks e bibliotecas de DI que podem gerenciar todos esses
casos e no Android, um dos mais usados ​é o Dagger 2.

Usando o Dagger 2 para gerenciar


dependências

Nesta seção, nósirá analisar a biblioteca Dagger 2, comoele lida com DI, como
funciona, como é integrado a um aplicativo Android e quais problemas ele
pode criar.

A biblioteca Dagger 2 depende da geração de código com base no processa-


mento de anotação, que gerará o código clichê necessário para executar a DI.
A biblioteca é escrita em Java e é usada para vários projetos fora dos aplicati-
vos Android. Por ser escrito em Java, ele oferece compatibilidade para aplicati-
vos escritos em Java, Kotlin ou ambos. A biblioteca é construída usando Java

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 6/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Specification Requests ( JSR ) 330 , que fornece um conjunto deanotações


para DI ( @Inject , @Named , @Qualifier , @Scope e @Singleton ).

Ao integrar o Dagger 2, existem três conceitos principais que precisaremos


considerar:

Provider : É representado pelas classes responsáveis ​por fornecer as de-


pendências, utilizando a anotação @Module para as classes e @Provides para
os métodos. Para evitar muitas definições de @Module , podemos usar a ano-
tação @Inject em um construtor, que fornecerá o objeto como uma
dependência.
Consumer : Isso é representado pelas classes onde as dependências são ne-
cessárias usando a anotação @Inject .
Connector : é representado pelas classes que conectam os provedores com
os consumidores e é anotado com a anotação @Component .

Para adicionarDagger 2 para um aplicativo Android, vocêprimeiro é necessá-


rio adicionar o plug-in do processador de anotações Kotlin ao arquivo
build.gradle do módulo no qual o Dagger 2 é usado:

plug-ins {
    …
    id 'kotlin-kapt'
    …
}
Aqui, adicionamos o plug-in kotlin-kapt para permitir que o Dagger 2 gere o
código necessário para DI. Em seguida, precisaremos das dependências do
Dagger 2:

dependências {
    …
    implementação 'com.google.dagger:dagger:2.40.5'
    kapt 'com.google.dagger:dagger-compiler:2.40.5'
    …
}
Aqui, estamos adicionando uma dependência à biblioteca Dagger 2 e uma de-
pendência à biblioteca de processamento de anotações, que tem a função de

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 7/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

geração de código. A versão da biblioteca deve ser a versão estável mais re-
cente disponível no repositório da biblioteca.

Vamos agorareintroduza o exemplo doseção anterior:

class ClassA(privado val b: ClassB) {


    divertido executeA() {
        b.executeB()
    }
}
interface Classe B {
    divertido executeB()
}
class ClassB1() : ClassB {
    substituir fun executeB() {
        // Faça alguma coisa
    }
}
class ClassB2() : ClassB {
    substituir fun executeB() {
        //Faça outra coisa
    }
}
Aqui, temos as mesmas classes com as mesmas dependências. Em vez de defi-
nir uma classe Injector , podemos usar o Dagger 2 para definir um @Module :

@Módulo
class ApplicationModule {
    @Provides
    fun provideClassA(b: ClassB): ClassA = ClassA(b)
    @Provides
    fun provideClassB(): ClassB = ClassB1()
}
Aqui, anotamosa classe com @Module e paracada instância, usamos a anotação
@Provides . Podemos simplificar ainda mais com a anotação @Inject e excluir

os métodos @Provides de ApplicationModule :

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 8/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

class ClassA @Inject constructor(private val b: ClassB) {


   …
}
class ClassB1 @Inject constructor() : ClassB {
    …
}
class ClassB2 @Inject constructor() : ClassB {
    …
}
No código anterior, adicionamos @Inject para cada construtor. No caso de
ClassA , ele terá tanto o papel de injetar ClassB quanto fornecer ClassA para

outros objetos como umdependência. Há, porém, uma questãoporque ClassA


tem uma dependência da abstração em vez da concreção, então Dagger não
saberá qual instância fornecer para ClassA . Agora podemos adicionar um mé-
todo anotado @Binds ao ApplicationModule , que conectará a abstração com a
implementação:

@Módulo
classe abstrata ApplicationModule {
    @Binds
    diversão abstrata bindClassB(b: ClassB1): ClassB
}
Aqui, adicionamos o método abstrato bindClassB , que é anotado com @Binds .
Este método dirá ao Dagger 2 para conectar a implementação ClassB1 com a
abstração ClassB . Para evitar grandes anotações @Provides , devemos tentar
usar a anotação para dependências onde não podemos modificar o código e,
em vez disso, confiar em @Inject nos construtores e usar @Binds sempre que
possível.

Agora, precisaremos criar o conector:

@Singleton
@Component(modules = [ApplicationModule::class])
interface ApplicationComponent
Aqui, estamos definindo um @Component no qual especificamos o módulo que o
aplicativo usará. A anotação @Singleton diz ao Dagger que todas as dependên-
cias neste componente viverão enquanto o aplicativo. Neste ponto, devemos

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 9/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

acionar uma compilação no aplicativo. Isso acionará a compilação, que gerará


uma classe DaggerApplicationComponent . Esta é uma implementação do Appli‐
cationComponent que o Dagger 2 irá manipular. Esta classe será usada para

criar todo o gráfico de dependência. No Android, precisamos de um ponto de


entrada para isso, que é representado pela classe Application :

class MeuAplicativo : Aplicativo() {


    componente lateinit var: ApplicationComponent
    substituir fun onCreate() {
        super.onCreate()
        componente = DaggerApplicationComponent.create()
    }
}
Aqui, na classe MyApplication , estamos usando DaggerApplicationComponent e
criando o gráfico de dependência. Isto irá percorrer todos os módulos emo
gráfico e invoque todos os métodos @Provides . A anotação @Component tem ou-
tro papel, que é definirinjeção de membro quando a injeção de construtor não
é possível. No Android, essa situação ocorre ao lidar com componentes do ci-
clo de vida, como atividades e fragmentos, pois não temos permissão para mo-
dificar os construtores padrão dessas classes. Para isso, podemos fazer o
seguinte:

@Singleton
@Component(modules = [ApplicationModule::class])
interface ApplicationComponent {
    fun injetar(mainActivity: MainActivity)
}
Em ApplicationComponent , adicionamos um método chamado inject e a
Activity onde queremos que a injeção seja realizada. Na classe MainActivity ,

precisaremos fazer o seguinte:

class MainActivity : AppCompatActivity() {


    @Injetar
    lateinit var a: ClassA
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 10/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

        (aplicação como
             MyApplication).component.inject(this)
        a.executeA()
    }
}
Aqui, precisaremos acessar a instância ApplicationComponent criada em
MyApplication e, em seguida, invocar o método inject de ApplicationCompo‐

nent . Isso inicializará a variável a com a instância Dagger 2 criada. Essa abor-

dagem tem um problema, no entanto, porque todas as dependências


viverãocomo o aplicativo. Isso significa que Adaga 2precisará manter as de-
pendências na memória quando elas não forem necessárias. O Dagger 2 ofe-
rece uma solução para isso na forma de escopos e subcomponentes. Podemos
criar um novo Scope, que dirá ao Dagger 2 para manter apenas certas depen-
dências enquanto uma Activity estiver ativa e, em seguida, aplicar esse Scope
a um Subcomponent, que lidará com um gráfico menor de dependências.

@Alcance
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
classe de anotação ActivityScope
Aqui, criamos uma nova anotação @Scope , que indicará que as dependências
durarão tanto quanto as atividades. Em seguida, usaremos @ActivityScope
para criar uma classe anotada @Subcomponent :

@ActivityScope
@Subcomponent(modules = [ApplicationModule::class])
interface MainSubcomponent {
    fun injetar(mainActivity: MainActivity)
}
Aqui, temosdefiniu um subcomponenteque usará ApplicationModule e possui
um método de injeção para injeção de campo em MainActivity . Depois disso,
precisaremos dizer ao Dagger 2 para criar MainSubcomponent , modificando Ap‐
plicationComponent :

@Singleton
@Componente
interface ApplicationComponent {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 11/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

    fun createMainSubcomponent(): MainSubcomponent


}
Aqui, removemos ApplicationModule de @Component e substituímos o método de
injeção por um método createMainSubcomponent , que permitirá que Dagger

crie MainSubcomponent . Por fim, precisaremos acessar MainSubcomponent em


MainActivity e injetar a dependência ClassA :

class MainActivity : AppCompatActivity() {


    @Injetar
    lateinit var a: ClassA
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        (aplicativo como MyApplication).component.
            createMainSubcomponent().inject(this)
        a.executeA()
    }
}
Aqui nósacesse a instância ApplicationComponent de MyApplication , crie Main‐
Subcomponent eem seguida, injete a dependência ClassA na variável a . O có-

digo gerado pelo Dagger 2 pode ser visto na pasta


{module}/build/generated/source/kapt/{build type} e será parecido com a fi-

gura a seguir:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 12/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Figura 4.2 – Classes de Adagas Geradas

Na figura anterior, podemos ver que o Dagger irá gerar a implementação para
a interface ApplicationComponent , bem como a implementação MainSubcompo‐
nent . Pordependências que precisarãoser injetado, ele irá gerar uma classe

Factory para criar a dependência. Onde estivermos injetando através dos

membros, ele criará uma classe Injector , que será responsável por definir o
valor na variável membro, como a classe MainActivity .

Nesta seção, discutimos a biblioteca Dagger 2 e como ela pode ser usada para
fornecer e injetar dependências. Por ser uma biblioteca usada em outros fra-
meworks além do Android, ela requer soluções específicas para injetar ativi-
dades e fragmentos, usando injetores de membros e subcomponentes. Uma
tentativa de corrigir isso foi através da introdução da biblioteca Android Dag-
ger, que tratou da criação de classes anotadas @Subcomponent e introduziu no-
vas anotações para indicar como os subcomponentes devem ser criados. Mais
recentemente, a introdução da biblioteca Hilt foi maiseficaz para resolver es-

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 13/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

ses problemas por meio desimplificando a quantidade de código que os desen-


volvedores precisam escrever e fornecendo melhor compatibilidade com com-
ponentes como ViewModel. Na seção a seguir, veremos a biblioteca Hilt e
como ela resolve esses problemas.

Usando Hilt para gerenciar


dependências

Nesta seção, nósdiscutirá a biblioteca Hilt DI, comopodemos usá-lo em um


aplicativo Android e os recursos extras que ele fornece em cima da biblioteca
Dagger 2.

Hilt é uma biblioteca construída em cima do Dagger 2 com foco específico em


aplicativos Android. Isso é para remover o código clichê extra que era necessá-
rio para usar o Dagger 2 em um aplicativo. O Hilt elimina a necessidade de
usar as classes anotadas @Component e @Subcomponent e, por sua vez, oferece no-
vas anotações:

Ao injetar dependências em classes Android, podemos usar


@HiltAndroidApp para classes Application , @AndroidEntryPoint para ativi-

dades, fragmentos, serviços, broadcast receivers e visualizações e


@HiltViewModel para ViewModels .

Ao usar a anotação @Module , agora temos a opção de usar @InstallIn e es-


pecificar uma classe anotada @DefineComponent , que representa o compo-
nente ao qual o módulo será adicionado. Hilt fornece um conjunto de com-
ponentes úteis para instalar módulos em:
@SingletonComponent : Isso fará com que as dependências vivam en-

quanto o aplicativo.
@ViewModelComponent : Isso fará com que as dependências vivam en-

quanto um ViewModel .
@ActivityComponent : Isso fará com que as dependências vivam en-

quanto uma Activity .


@FragmentComponent : Isso fará com que as dependências vivam en-

quanto um Fragment .
@ServiceComponent : Isso fará com que as dependências vivam enquanto

um Service .

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 14/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Em ordem deusar o Hilt em um projeto, ele exigirá um plugin Gradle, quepre-


cisará ser adicionado como uma dependência ao arquivo root build.gradle no
projeto:

script de construção {
    repositórios {
        …
    }
    dependências {
        …
        classpath 'com.google.dagger:hilt-android-gradle-
            plug-in: 2.40.5'
    }
}
Em seguida, precisaremos adicionar o plug-in do processador de anotações e o
plug-in Hilt ao arquivo build.gradle do módulo Gradle no qual queremos usar
a biblioteca Hilt:

plug-ins {
    …
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
A combinação destes dois plugins é o que permite ao Hilt gerar a fonte neces-
sáriacódigo para injetar as dependências. Finalmente, nósprecisará adicionar
a dependência à biblioteca Hilt:

dependências {
    …
    implementação 'com.google.dagger:hilt-android:2.40.5'
    kapt 'com.google.dagger:hilt-compiler:2.40.5'
    …
}
Aqui, precisamos da dependência da própria biblioteca e uma dependência do
processador de anotações, como era necessário para o Dagger 2.

Vamos agora reintroduzir o exemplo da seção anterior:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 15/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

class ClassA @Inject constructor(private val b: ClassB) {


    divertido executeA() {
        b.executeB()
    }
}
interface Classe B {
    divertido executeB()
}
class ClassB1 @Inject constructor() : ClassB {
    substituir fun executeB() {
        // Faça alguma coisa
    }
}
class ClassB2 @Inject constructor() : ClassB {
    substituir fun executeB() {
        //Faça outra coisa
    }
}
Aqui nóspodemos manter a mesma estrutura de nossas classes euse a anota-
ção @Inject como anteriormente. A classe anotada @Module que fornecerá es-
sas dependências será semelhante a um módulo Dagger 2:

@Módulo
@InstallIn(SingletonComponent::class)
classe abstrata ApplicationModule {
    @Binds
    diversão abstrata bindClassB(b: ClassB1): ClassB
}
Na classe ApplicationModule , mantemos a mesma implementação de antes,
mas agora adicionamos a anotação @InstallIn , que tornará as dependências
fornecidas por este módulo ativas enquanto o aplicativo estiver. Em seguida,
precisaremos acionar a geração de componentes:

@HiltAndroidApp
class MeuAplicativo: Aplicativo()
Aqui, não precisamos mais usar DaggerApplicationComponent para acionar ma-
nualmente ocriação do gráfico de dependência e, em vez disso,use

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 16/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

@HiltAndroidApp , que fará isso por nós, além de fornecer a capacidade de inje-

tar dependências na classe MyApplication . Por fim, precisaremos injetar as de-


pendências em uma Activity :

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Injetar
    lateinit var a: ClassA
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        a.executeA()
    }
}
Aqui, usamos o ponto @AndroidEntry para informar ao Hilt que queremos inje-
tar uma dependência em uma Activity e, em seguida, usar a anotação @Inject
como funcionava no Dagger 2. O código gerado pelo Hilt será semelhante à fi-
gura a seguir e pode ser encontrado em
{module}/build/generated/source/kapt/{build type} :

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 17/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Figura 4.3 – Classes Hilt geradas

Na figura anterior, podemos ver classes de fábrica como as que o Dagger 2


gera, masclasses extras que Hilt irá gerar paralidar com o trabalho padrão que
era necessário para trabalhar com o Dagger 2, como manipular a injeção em
atividades e fragmentos ou criar o gráfico de dependência na classe Applica‐
tion .

Nesta seção, discutimos a biblioteca Hilt, como podemos usá-la para gerenciar
dependências em um aplicativo Android e como ela remove o código padrão
que o Dagger 2 exigia. Na seção a seguir, veremos um exercício sobre a inte-
gração do Hilt em um aplicativo junto com outras bibliotecas.

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 18/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Exercício 04.01 – Usando Hilt para gerenciar


dependências

Modifique o Exercício 03.02 – navegando usando o Jetpack Compose do Capítulo


3 , Entendendo a apresentação de dados no Android , para que ele use o Hilt
para gerenciar as dependências no aplicativo.

Completaro exercício, você precisará fazer oSegue:

1. Adicione a biblioteca Hilt ao projeto.


2. Crie uma classe NetworkModule que fornecerá as dependências de Retrofit.
3. Crie uma classe PersistenceModule que fornecerá as dependências de Room
e Data Store.
4. Limpe a classe MyApplication , exclua a classe MainViewModelFactory e, em
vez disso, use a anotação @HiltViewModel .
5. Modifique MainActivity para obter uma instância do modelo MainView da
biblioteca Hilt Compose Navigation.

Siga estas etapas para concluir o exercício:

1. Adicione o plug-in Hilt Gradle ao arquivo build.gradle do projeto raiz :


script de construção {
    repositórios {
        …
    }
    dependências {
        …
        classpath 'com.google.dagger:hilt-android-
            gradle-plugin: 2.40.5'
    }
}
2. Aplicar oPlug-in do Gradle para o arquivo build.gradle emo módulo do
aplicativo:
plug-ins {
    …
    id 'dagger.hilt.android.plugin'
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 19/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

3. Adicione a dependência da biblioteca Hilt ao arquivo build.gradle do mó-


dulo do aplicativo :
dependências {
    …
    implementação 'com.google.dagger:hilt-android
        :2.40.5'
    kapt 'com.google.dagger:hilt-compiler:2.40.5'
    implementação 'androidx.hilt:hilt-navigation-
        composição:1.0.0-rc01'
    …
}

Aqui, adicionamos uma dependência que permite que o Hilt trabalhe com a
biblioteca Jetpack Compose Navigation.

4. Crie uma classe NetworkModule na qual as dependências de rede são


fornecidas:
@Módulo
@InstallIn(SingletonComponent::class)
class Módulo de Rede {
    @Provides
    fun fornecerOkHttpClient(): OkHttpClient =
        OkHttpClientName
        .Construtor()
        .readTimeout(15, TimeUnit.SECONDS)
        .connectTimeout(15, TimeUnit.SECONDS)
        .construir()
    @Provides
    fun fornecerMoshi(): Moshi = Moshi.Builder().
        add(KotlinJsonAdapterFactory()).build()
    @Provides
    fun fornecerRetrofit(okHttpClient: OkHttpClient,
        moshi: Moshi): Retrofit = Retrofit.Builder()
        .baseUrl("https://jsonplaceholder.typicode.com
/")
        .client(okHttpClient)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 20/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

        .addConverterFactory(MoshiConverterFactory.cre
ate
    (moshi))
        .construir()
    @Provides
    fun fornecerUserService(retrofit: Retrofit):
        Serviço de usuário =
            retrofit.create(UserService::class.java)
}

Aqui, temosmoveu todas as dependênciaspara rede e divida-os em métodos se-


parados para OkHttplClient , Moshi , Retrofit e, finalmente, a classe UserSer‐
vice .

5. Em seguida, crie uma classe PersistenceModule , que retornará todas as de-


pendências relacionadas à persistência:
val Context.dataStore: DataStore<Preferences> por
preferencesDataStore(name = "my_preferences")
@Módulo
@InstallIn(SingletonComponent::class)
class PersistênciaModule {
    @Provides
    divertido fornecerAppDatabase(@ApplicationContext
        contexto: Contexto): AppDatabase =
        Room.databaseBuilder(
            contexto,
            AppDatabase::class.java, "meu-banco de
dados"
        ).construir()
    @Provides
    fun provideUserDao(appDatabase: AppDatabase):
        UserDao = appDatabase.userDao()
    @Provides
    divertido fornecerAppDataStore(@ApplicationContext
        contexto: Contexto) = AppDataStore
            (context.dataStore)
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 21/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Aqui, movemos todas as classes relacionadas ao Room e as classes do Data


Store. Para DataStore , somos obrigados a declarar o arquivo
Context.dataStore no nível superiordo arquivo, então precisaremos

manterisso aqui. O uso de @ApplicationContext destina-se a denotar que o ob-


jeto Context é representado pelo contexto do aplicativo e não por outros obje-
tos Context, como um objeto Activity ou Service . A anotação é um Qualifier ,
que serve para distinguir entre diferentes instâncias da mesma classe (neste
caso, é para distinguir entre o contexto do aplicativo e o contexto da
atividade).

6. Adicione a anotação @Inject ao construtor da classe MainTextFormatter :


class MainTextFormatter @Inject
constructor(@ApplicationContext private val
applicationContext: Context) {
    fun getCounterText(count: Int) =
        applicationContext.getString(R.string.total_
            request_count, contagem)
}

Isso permitirá que o Hilt forneça uma nova instância de MainTextFormatter


toda vez que for usado como uma dependência. Aqui, novamente, precisare-
mos usar a anotação @ApplicationContext para usar o objeto Context do aplica-
tivo .

7. Excluir tudoas dependências na classe MyApplicatione adicione a anotação


@HiltAndroidApp :
@HiltAndroidApp
class MeuAplicativo: Aplicativo()
8. Exclua a classe MainViewModelFactory .
9. Adicione a anotação @HiltViewModel à classe MainViewModel e @Inject ao
construtor:
@HiltViewModel
class MainViewModel @Inject construtor (
    valor privado userService: UserService,
    valor privado userDao: UserDao,
    valor privado appDataStore: AppDataStore,
    valor privado mainTextFormatter: MainTextFormatter

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 22/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

) : ViewModel() {
   …
}
10. Exclua a referência a MainViewModelFactory no método Users @Composable
em MainActivity :
@Composable
usuários divertidos (
    navController: NavController,
    viewModel: MainViewModel
) {
    …
}
11. Mudaro método @Composable Appem MainActivity para que ele forneça uma
instância MainViewModel ao invocar o método Users :
@Composable
App divertido(navController: NavHostController) {
    NavHost(navController, startDestination =
AppNavigation.Users.route) {
        composable(route = AppNavigation.Users.route)
{
            Usuários(navController, hiltViewModel() )
        }
        combinável(
            rota = AppNavigation.User.route,
            argumentos = listOf(navArgumento
                (AppNavigation.User.argumentName) {
                tipo = NavType.StringType
            })
        ) {
            User(it.arguments?.getString(AppNavigation
.User.
    argumentName).ouEmpty())
        }
    }
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 23/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

Aqui, estamos usando o método hiltViewModel , que é da biblioteca de compa-


tibilidade Hilt com a biblioteca Navigation.

12. Adicione a anotação @AndroidEntryPoint a MainActivity :


@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    …
}
13. Se vocêencontrar um erro de Registros requer ASM8ao construir o aplica-
tivo, adicione o seguinte ao arquivo gradle.properties do projeto raiz :
android.jetifier.ignorelist=moshi-1.13.0

Esse erro é causado por uma incompatibilidade que existe atualmente nas fer-
ramentas de compilação do Android e deve ser resolvido quando atualizações
posteriores estiverem disponíveis.

Se executarmos o aplicativo abordado neste exercício, a funcionalidade e a in-


terface do usuário devem permanecer as mesmas de antes. O papel do Hilt
aqui foi simplificar como gerenciamos as dependências, mostrado pela forma
como simplificamos a classe MyApplication , deixando-a com uma anotação
simples, e pelo fato de termos removido MainViewModelFactory , que por si só
tinha que depender da classe MyApplication . Também podemos ver como é fá-
cil integrar o Hilt com o restante das bibliotecas que usamos no exercício.

Resumo

In this chapter, we looked at the DI pattern and some of the more popular li-
braries that are available to apply this pattern to an Android application. We
looked initially at Dagger 2 and how it can be integrated into an application,
and then we analyzed the Hilt library, which is built on top of Dagger 2 and
solves further problems that are specific to Android development.

Existem outras bibliotecas que podem ser usadas para gerenciar dependên-
cias, como a Koin, que usa o padrão Service Locator (no qual um registro é cri-
ado e as dependências podem ser obtidas) e é desenvolvida para desenvolvi-
mento Kotlin. O exercício deste capítulo mostrou como o Hilt pode ser inte-
grado a outras bibliotecas em um aplicativo Android. O problema é que o apli-

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 24/25
26/10/2022 21:43 Chapter 4: Managing Dependencies in Android Applications | Clean Android Architecture

cativo ainda não tem forma; não há nada que possamos apontar que indique
quais são os casos de uso. Nos capítulos a seguir, veremos como podemos es-
truturar nosso código para dar-lhe uma forma usando os princípios da Arqui-
tetura Limpa, começando com a definição de entidades e casos de uso.

Apoiar Sair

© 2022 O'REILLY MEDIA, INC.  TERMOS DE SERVIÇO POLÍTICA DE PRIVACIDADE

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_04_ePub.xhtml 25/25

Você também pode gostar