Aplicando Clean Code em Android Kotlin com injeção de dependência usando arquitetura MVVM

Aplicando Clean Code em Android Kotlin com injeção de dependência usando arquitetura MVVM

Olá! Como vai? O Clean Code e a arquitetura MVVM são requisitos muito pedidos em vagas de desenvolvimento Android, e por isso é essencial conhecer não apenas os conceitos, mas também como aplicá-los, pois muitas vagas de desenvolvimento Android exigem etapa de teste técnico, onde o candidato deve mostrar as melhores práticas com as melhores ferramentas.

Por isso, não temas! Esse artigo te mostra como aplicar o conceito de Clean Code em Android usando Kotlin, através da arquitetura MVVM e injeção de dependência com Dagger Hilt 2, biblioteca recomendada pelo Jetpack da Google (pacote de bibliotecas recomendadas https://developer.android.com/jetpack).

Mas afinal, o que é Clean Code?

Clean code é um estilo de escrita de código que enfatiza a legibilidade, manutenção e simplicidade. Um código limpo, fácil de entender e modificar, pois é bem organizado, usa nomes descritivos para variáveis e funções, segue convenções de estilo consistentes e é estruturado de forma lógica e clara.

O objetivo do clean code é tornar o processo de desenvolvimento de software mais eficiente e menos propenso a erros. Ao escrever código limpo, os desenvolvedores podem reduzir a complexidade do sistema, melhorar a legibilidade e compreensão do código e torná-lo mais fácil de manter e atualizar ao longo do tempo.

Algumas das práticas comuns de clean code incluem:

- Usar nomes significativos para variáveis, funções e classes;

- Escrever funções pequenas e coesas;

- Evitar duplicação de código;

- Comentar apenas quando necessário e de maneira clara e objetiva;

- Escrever testes automatizados para validar o código;

- Usar espaçamento consistente e indentação apropriada para facilitar a leitura do código.

E o que Clean Code tem a ver com MVVM?

Tudo! A arquitetura MVVM (Model-View-ViewModel) é um padrão de arquitetura de software que corresponde com os principais conceitos de Clean Code.

- Separação de responsabilidades: O MVVM divide a aplicação em três componentes principais - o modelo (Model), a interface do usuário (View) e o ViewModel. Cada componente tem uma responsabilidade claramente definida. O modelo é responsável pela lógica de negócios e persistência de dados, a interface do usuário é responsável pela exibição de dados e interação com o usuário, e o ViewModel é responsável por intermediar entre o modelo e a interface do usuário. Essa separação de responsabilidades ajuda a manter o código organizado, legível e fácil de manter.

- Testabilidade: O MVVM torna a aplicação mais testável, pois o ViewModel é projetado para ser independente da interface do usuário e pode ser facilmente testado sem a necessidade de uma interface do usuário. Além disso, o modelo também pode ser testado separadamente do ViewModel e da interface do usuário. A capacidade de testar diferentes componentes da aplicação separadamente ajuda a garantir que cada componente funcione corretamente e evita problemas de regressão.

- Facilidade de manutenção: Por fim, o MVVM também ajuda na manutenção da aplicação, pois é fácil adicionar novos recursos ou alterar os recursos existentes sem afetar o restante da aplicação. O ViewModel é projetado para ser independente da interface do usuário, o que significa que as mudanças na interface do usuário não afetam o ViewModel. Além disso, a separação de responsabilidades torna mais fácil para os desenvolvedores entenderem como a aplicação funciona e fazer alterações sem afetar outros componentes. Isso ajuda a manter o código limpo e organizado.

Veja na imagem abaixo o conceito de MVVM organizado em diagrama por camadas:

Fonte: Google (https://developer.android.com/training/dependency-injection/manual)‌ ‌


Você deve estar se perguntando, o que é esse "Repository" no meio do diagrama?

O Repository é responsável por fornecer dados para a camada ViewModel. Ele representa a camada de abstração de várias fontes de dados, chamada de camada Data Source, podendo ser banco de dados local ou serviço web remoto, e então o Repository fornece uma interface unificada para a camada ViewModel.

Na óptica Clean Code, o Repository promove o princípio de responsabilidade única, pois tem apenas uma responsabilidade, que é fornecer dados para a camada ViewModel. Além disso, a camada Repository facilita a implementação de testes, pois através dela pode-se mockar os dados, não sendo necessário o uso dos dados reais nos testes.

Como vamos aplicar todos esses conceitos juntos?

Aplicar Clean Code com MVVM vai requerer criar uma série de classes, objetos e interfaces, por isso é recomendado o uso de injeção de dependência, pois assim podemos injetar as classes onde precisamos usá-las e assim podemos aplicar as camadas do MVVM. Vamos implementar!

Será mostrado aqui como implementar um simples dado de N lados aplicando os conceitos que vimos acima, a aplicação retornará o resultado de rolar os dados ao clicar em um botão, esse resultado vai ser calculado aleatoriamente. Atenção, não iremos usar nenhuma fonte de dado local ou remota, pois o foco é o Clean Code com MVVM.

Etapa 1. Incluir dependências

Para construir nosso projeto com Dagger Hilt precisamos incluir essa biblioteca nas dependências do projeto Android. Primeiro vamos no build.gradle do projeto e vamos incluir:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

plugins {

   ...

   id 'com.google.dagger.hilt.android' version '2.41' apply false

}



Agora no build.gradle (:app) adicione o "kotlin-kapt" e as dependências necessárias:

plugins {

   ...

   id 'kotlin-kapt'

   id 'com.google.dagger.hilt.android'

}


dependencies {


   ...


   // Dagger Hilt

   implementation "com.google.dagger:hilt-android:2.41"

   kapt "com.google.dagger:hilt-android-compiler:2.41"


   // Activity Extensions (to use 'by viewModels()'

   implementation "androidx.activity:activity-ktx:1.7.1"

}


A dependência "androidx.activity:activity-ktx:version" vai servir para facilitar a injeção de ViewModel, será visto na prática quando formos implementar a View.  

Etapa 2. Criar estrutura de diretórios e classes abaixo

Essa estrutura é tipicamente encontrada em projetos reais do mercado, criamos os pacotes para cada camada para facilitar o entendimento das mesmas e também para permitir que elas escalem de forma ordenada, você sempre poderá adicionar mais repositories, datasources, models e UIs de acordo com a necessidade.


Etapa 3. Inicializar o Dagger Hilt na classe App

O Dagger Hilt precisa ser iniciado através de um Custom Application, deve-se aplicar a notação @HiltAndroidApp, essa e muitas outras notações são essenciais para construir um app com Dagger Hilt, que se baseia nas notações para construir a árvore de injeção de dependências.

@HiltAndroidApp

class App: Application()


Precisamos atualizar o AndroidManifest.xml, indicando que iremos usar a classe App como custom application.

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

   xmlns:tools="http://schemas.android.com/tools">


   <application

       android:name=".App"

       ...>

       ...

   </application>


</manifest>



Etapa 4. Crie o Model do dado com a classe Dice

O Model é apenas uma classe do tipo data que serve para definir como é o objeto dado e quais seus atributos, no nosso caso, o dado só possui o atributo "faces", que determina quantos lados tem o dado.

data class Dice(

   var faces: Int

)


Etapa 5. Implementar o DataSource

Agora vamos implementar o Data Source, responsável por se comunicar com a fonte de dados (podendo se local ou remota), que nesse exemplo será apenas uma função para pegar um valor aleatório entre 1 e o total do número de faces do dado.

Aqui a notação @Inject é importante no constructor para que a classe seja vista pela injeção de dependência do Dagger Hilt, sem essa notação não podemos injetar nada dentro do DiceDataSource, e nem injetar o DiceDataSource dentro de outra classe.

class DiceDataSource @Inject constructor() {


   fun rollDice(dice: Dice) = (1..dice.faces).random()

}


Etapa 6. Implementar o Repository

Finalmente vamos implementar o Repository, classe responsável por se comunicar com o Data Source, essa camada irá abstrair para o ViewModel apenas o resultado da fonte de dados, não sendo necessário saber de onde e como ela vem.

Novamente a notação @Inject, porém agora no constructor teremos o DiceDataSource como parâmetro, fazendo apenas o uso correto dessa notação já é suficiente para não nos preocuparmos com a injeção, pois o Dagger Hilt dá conta do resto!

class DiceRepository @Inject constructor(

   private val dataSource: DiceDataSource

) {


   fun rollDice(dice: Dice) = dataSource.rollDice(dice)

}


Etapa 7. Implementar o ViewModel

Abaixo temos a implementação do ViewModel, que irá injetar a camada Repository e vai expor o resultado da rolagem de dado através do LiveData diceResult, e irá permitir a ação de rolagem de dados para disparar a função rollDice do DiceRepository através de uma função de mesmo nome "rollDice".

Perceba que para fazer o bind correto de uma classe do tipo ViewModel, o Dagger Hilt precisa ver a notação @HiltViewModel.

@HiltViewModel

class MainViewModel @Inject constructor(

   private val diceRepository: DiceRepository

): ViewModel() {


   private var _diceResult = MutableLiveData<Int?>(null)

   var diceResult: LiveData<Int?> = _diceResult


   fun rollDice(dice: Dice) {

       _diceResult.value = diceRepository.rollDice(dice)

   }

}

Faremos com que o resultado do diceRepository.rollDice(dice) atualize o valor do MutableLiveData _diceResult, que por sua vez atualiza o valor do LiveData diceResult, fazemos dessa forma para que a View não seja capaz de alterar o valor do resultado, pois a View deve apenas escutar o valor e reagir a ele.

Etapa 8. Implementar a View

Concluindo nossa implementação por camadas, agora vamos ver a View, que será uma Activity com uma TextView e um Button. Para o Dagger Hilt injetar tudo corretamente o que é necessário em uma View, é preciso usar a notação @AndroidEntryPoint.

E agora para injetar o ViewModel na View, usaremos o artifício "by viewModels()", pois é elegante e sucinto, e diferente do uso do ​​ViewModelProvider, não é necessário a criação de uma Factory, pois isso tudo é feito automaticamente.

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {


   private val mainViewModel: MainViewModel by viewModels()


   private lateinit var diceResultText: TextView

   private lateinit var rollDiceButton: Button


   override fun onCreate(savedInstanceState: Bundle?) {

       super.onCreate(savedInstanceState)

       setContentView(R.layout.activity_main)


       diceResultText = findViewById(R.id.diceResultText)

       rollDiceButton = findViewById(R.id.rollDiceButton)


       rollDiceButton.setOnClickListener {

           mainViewModel.rollDice(Dice(20))

       }


       mainViewModel.diceResult.observe(this) {

           it?.let { diceResultText.text = it.toString() }

       }

   }

}

Perceba que no adicionar no botão o OnClickListener para disparar a função rollDice do MainViewModel usando um dado de 20 faces, e logo em seguida registramos a observação do LiveData diceResult para que a View reaja a mudanças de valor nesse objeto.

Etapa 9. Role os dados testando o app!

Demo: https://www.youtube.com/watch?v=1uVZOkRyQ3s

Perceba como é simples trocar a camada de Data Source para um dado provindo de um banco de dados ou de um banco remoto através de uma API sem tocar na camada de View, pois o Repository abstrai apenas o que a View precisa enxergar.

É possível também atualizar a View para um layout mais moderno e bonito sem se preocupar com a camada de dados e sem o risco de danificar algo em outra parte do código que não seja a própria View.

O Clean Code facilita a manutenção e a inclusão de novos itens, pois digamos que agora queremos 2 ou mais dados aparecendo na tela, podemos incluir ele através da View sem mexer na fonte de dados também. Outra possibilidade seria adicionar um input de texto para receber a quantidade de faces que o usuário deseja no dado. Sinta-se livre para explorar esse código e atuar nas diversas camadas.

Conclusão

É claro que o exemplo acima é simplório e seria muito mais simples apenas usar a operação de rolar dados random ao clicar no botão, esse caso não justifica o uso de Clean Code com MVVM e injeção de dependências, foi apenas um exemplo prático de implementação para não complicar a demonstração dos conceitos.

Lembre-se que o Clean Code e o MVVM devem ser utilizados em casos de uso que fazem sentido para tal, como por exemplo:

- Lista de itens: um caso de uso comum em aplicativos Android é a exibição de uma lista de itens, como mensagens em um aplicativo de e-mail ou produtos em um aplicativo de comércio eletrônico. O uso de clean code e MVVM pode ajudar a garantir que o código relacionado a essa funcionalidade esteja bem estruturado, com uma camada de modelo de dados clara e uma separação clara entre a lógica de visualização e o código de negócios.

- Formulários de entrada de dados: outro caso de uso comum é a entrada de dados do usuário em formulários. O uso de clean code e MVVM pode ajudar a garantir que a validação de dados seja feita de forma clara e consistente em todo o aplicativo, tornando mais fácil para o desenvolvedor lidar com possíveis erros.

- Autenticação de usuário: um caso de uso importante em muitos aplicativos Android é a autenticação de usuários. O uso de clean code e MVVM pode ajudar a garantir que a lógica de autenticação seja separada claramente da lógica de visualização, o que torna o código mais fácil de testar e manter. Comunicação com APIs: em muitos aplicativos Android, é necessário fazer chamadas a APIs para recuperar dados de back-end. O uso de clean code e MVVM pode ajudar a garantir que a lógica de comunicação com a API seja claramente separada da lógica de visualização e do código de negócios.

No mercado de trabalho em aplicativos reais, você com certeza encontrará casos de uso reais que aplicam MVVM com Clean Code, e agora concluindo a leitura desse artigo, você saberá o que está acontecendo por trás dessa arquitetura e poderá executar um bom trabalho.

Gostou desse artigo? Então adicione essa página aos seus favoritos para consultar quando necessário, e continue estudando e praticando o uso de Clean Code, MVVM e Dagger Hilt. Torne-se um profissional desejado pelo mercado de trabalho para vagas Android.

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.