Introdução

Apesar do nome (injeção), a Injeção de Dependências (Dependency Injection - DI) não dói e faz muito bem! A DI tem um papel fundamental dentro da "Arquitetura Limpa", ajudando no desacoplamento, simplificação e teste do projeto.

A DI é usada, principalmente, para alcançar a famosa "Inversão de Controle" (Inversion of Control - IoC), que faz parte do SOLID ¹. A ideia por trás do IoC é de "se eu precisar, eu peço!", ou seja, sempre que um objeto precisar de outro, ele pedirá ao framework ao invés de simplesmente cria-lo. Dessa forma, a responsabilidade de criar/gerir instâncias dos objetos não é mais de quem os utiliza, mas sim do framework que realiza a DI.

Há alguns frameworks disponíveis para utilizar DI, sendo Dagger2 e Koin os mais famosos e consolidados na comunidade Android, caso haja curiosidade sobre a diferença de ambos, esse artigo pode te ajudar! Nesse artigo em específico utilizarei o Dagger2.


Injeção de Dependências

Vamos supor que existe uma Classe A que é utilizada em diversos locais do App. Por algum motivo sádico do destino, agora essa classe recebe mais 2 parâmetros, fazendo com que você, pobre desenvolvedor, tenha que encontrar cada uma de suas instâncias para fazer as devidas modificações. É fácil perceber que esse trabalho, em projetos maiores, pode causar muitos problemas, já que as chances de alguma modificação não sair como esperado é grande.

Imagem 1

Para resolver esse problema, usamos Injeção de Dependências, ou seja, existe um "gerenciador" de dependências que sabe como instanciar as classes que serão utilizadas no projeto, ou seja, sempre que for necessário criar ou reaver um Objeto, basta pedir ao gerenciador.

Imagem 2

A imagem acima exibe a ideia da DI, nela é possível ver que todos os objetos são criados pelo gerenciador. Quando A1 precisar de B1, ele irá pedir a instância dessa classe para o gerenciador, e não cria-la manualmente. Essa estrutura é muito útil, uma vez que centraliza a criação de objetos em apenas um ponto, logo, sempre que for necessário modificar algum objeto, só precisamos modifica-lo em um lugar! Além disso, há uma desacoplamento entre os objetos, uma vez que todo novo objeto utilizado será injetado e não criado.


Dagger2

O Dagger2 nada mais é do que uma biblioteca, que gera código automaticamente, utilizada como framework de DI nos projetos Android.

Como funciona?

O Dagger (irei referenciar o Dagger2 apenas como Dagger daqui em diante) funciona como uma criança, é necessário ensina-lo a fazer tudo! Então, caso haja interesse em injetar uma classe específica, é necessário ensina-lo como instanciar a classe e onde essa instância será utilizada. Para isso, um conjunto de Módulo/Componente (Module/Component) é utilizado.

No Módulo, definimos como as classes devem ser criadas e quais são suas dependências. Dentro da classe do Módulo, sempre que um método for utilizado pelo Dagger, esse deve ser acompanhado da anotação @Provides.

@Module
class ApplicationModule {
    @Provides
    fun getDoorClass() = Door()

    @Provides
    fun getGlassClass() = Glass()

    @Provides
    fun getPeopleClass() = People()

    @Provides
    fun getWindowClass(glass: Glass) = Window(glass)

    @Provides
    fun getHouseClass(door: Door, people: People, window: Window) = House(door, window, people)
}
Código 1

Os Componentes, que são responsáveis por "expor" o que o Dagger sabe para o mundo, utilizam os Módulos para instanciar os objetos que serão injetados. Ou seja, são eles que farão o "meio de campo" entre quem precisa e quem sabe criar os objetos.

@Component(modules = [ApplicationModule::class])
interface ApplicationComponent{
    fun inject(mainActivity: MainActivity)
}
Código 2

No Componente do código 2, que faz uso do Módulo do código 1, possui um método inject que será usado como gatilho da injeção de dependências.

private  val component : ApplicationComponent? by lazy {
    DaggerApplicationComponent.builder().build()
}

@Inject
lateinit var house: House

@Inject
lateinit var window: Window

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    component?.inject(this)
}
Código 3

Uma vez que o Componente e o Módulo estão prontos, basta realizar o build da implementação do Component e injetar os objetos utilizados pela MainActivity. Para injetar um objeto, a anotação @Inject deve ser usada em uma lateinit var. E é isso, agora temos um exemplo simples, mas funcional, do Dagger 2!


Prós e Contras

Alguns prós e contras de se usar o Dagger como framework de DI podem ser encontrados abaixo:

Prós
  • Amigável aos testes;
  • Modularizado;
  • Manutenção facilitada.
Contras
  • Geração de código;
  • Curva de aprendizado pode ser alta;
  • Não é possível utilizar variáveis privadas.

Conclusão

Apesar de ser um pouco amedrontador no começo, o Dagger é uma ferramenta muito útil e que possibilita criar um código mais limpo, desacoplado e fácil de testar. Porém, como todos sabemos, grandes poderes trazem grandes responsabilidades e, por conta disso, é necessário um controle sobre o quão desacoplado o código deverá ficar, cabendo ao desenvolvedor decidir quanto, e quando, a DI deverá ser utilizada, sempre valendo da ideia de que a diferença entre veneno e remédio é a quantidade.

Em suma, DI é um conceito relativamente simples, que pode ajudar na construção de aplicações mais coesas, rápidas e eficientes, além de ajudar o desenvolvedor a sanar tarefas repetitivas e chatas. Todo o código exemplo pode ser encontrado aqui, quaisquer dúvidas, não deixe de pesquisar e/ou perguntar no campo de comentário.

Obrigado pela atenção e até uma próxima!

Referências

  1. https://mobyleofficial.medium.com/s-o-l-i-d-amor-e-carinho-e03f01e805e5