Melhorando a arquitetura da Pokédex
Tags: kotlin, android, cleancode, pokemon
Published: 2022-11-11 02:38:26

Nesse post nós vamos usar o `Koin` para delegar a injeção das dependências da nossa aplicação, como Retrofit, API service, repositórios e ViewModels e implementar a arquitetura limpa no nosso projeto com interfaces e use cases.

## Clean Architecture

A **Clean Architecture** (Arquitetura Limpa) é, como o próprio nome diz, uma arquitetura de desenvolvimento de software que visa focar no domínio da aplicação; sendo os drivers, frameworks e libraries apenas detalhes da aplicação. O objetivo é o principio da responsabilidade única, separando o interesse de cada módulo e mantendo as regras de negócio sem conhecer qualquer detalhe sobre o mundo exterior; assim, eles podem ser testados sem dependência de qualquer elemento externo.


![Clean Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0ty0h7pd55dss8h7hi4.jpg)

De forma resumida, o nosso sistema será dividido em três camadas:

- **Presentation (módulo Android):** responsável pela interface do aplicativo e a exibição dos dados recebidos do domínio.
- **Domain (módulo Kotlin):** responsável pelas entidades e as regras de domínio específicas do projeto. Esse módulo deve ser totalmente independente da plataforma
Android.
- **Infrastructure (módulo Android):** responsável pelo banco de dados, acesso a internet e outros “detalhes” da aplicação.

Na camada **presentation** temos as _Activities_ e _Fragments_, não deve haver lógica dentro delas que não seja a lógica de UI.

Por outro lado, na camada **domain** estão as entidades, interfaces dos _repositories_ e *use cases*, é aqui que fica nossa lógica de negócios e serve como ponte entre a camada *presentation* e a **infrastructure**.

Por fim, na camada **infrastructure** estão os dados necessários para a nossa aplicação (chamadas à uma API Rest no nosso caso) que são acessados a partir dos *repositories* definidos na camada **domain**.

Então o que muda? Bem, já estávamos fazendo algo parecido com isso, mas nossos *repositories* não estão separados em contratos (interfaces) e implementações e nem estamos usando *use cases* para acessar as implementações dos *repositories* e mandar os dados para a camada **presentation**.

Para unir todos esses módulos e fazer a arquitetura funcionar, vamos usar o framework `Koin` para aplicar a *Dependency Inversion Principle* (Princípio da Inversão da dependência), o *D* do *SOLID*. Esse princípio diz que módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender da abstração e abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Ou seja, em várias partes do nosso código, o *repository* estava responsável por criar uma instância de um *service*, uma *ViewModel* estava responsável por criar uma instância de um **repository*, isso muda agora.

Obs: explicação da *Clean Architecture* retirada do excelente [artigo](https://medium.com/android-dev-br/clean-architecture-para-android-eb492513263e) do [Marcello Galhardo](https://medium.com/@marcellogalhardo?source=post_page-----eb492513263e--------------------------------).

## Usando o `Koin`

`Koin` é um framework leve de injeção de dependência totalmente escrito em Kotlin, sendo bem fácil de aprender e usar. Para usá-lo, precisamos entender as suas terminologias:

- **module**: cria um módulo em `Koin` que pode ser usado para prover todas as dependências.
- **single**: cria um singleton que pode ser usado em todo o app como uma uma instância singular.
- **factory**: provê uma definição `bean`, a qual vai criar uma nova instância cada vez que ela é injetada.
- **get()**: é usada no construtor da classe que provê a dependência necessária.

Agora vamos adicionar o `Koin` ao nosso projeto por meio do arquivo `build.gradle`:

```
def koinVersion = "3.2.2"

dependencies {
	...

  // Koin
  implementation "io.insert-koin:koin-android:$koinVersion"
  implementation "io.insert-koin:koin-android-compat:$koinVersion"
  implementation "io.insert-koin:koin-androidx-workmanager:$koinVersion"
  implementation "io.insert-koin:koin-androidx-navigation:$koinVersion"	

	...
}
```

Para começarmos a usar o `Koin` no nosso projeto, primeiro de tudo, precisamos criar uma interface para nosso `PokemonRepository`, fazemos isso para obedecer aos princípios da arquitetura limpa.

```kotlin
package br.com.pokedex.domain.repository

import br.com.pokedex.domain.model.SinglePokemon

interface PokemonRepository {

    suspend fun getSinglePokemon(id: Int): SinglePokemon
}
```

Classe `PokemonRepository` vai ser renomeada para `PokemonRepositoryImpl` e modificada:

```kotlin
package br.com.pokedex.data.repository

import br.com.pokedex.data.api.PokemonApi
import br.com.pokedex.data.mapper.toModel
import br.com.pokedex.domain.repository.PokemonRepository

class PokemonRepositoryImpl(private val api: PokemonApi) : PokemonRepository {

    override suspend fun getSinglePokemon(id: Int) = api.getSinglePokemon(id).toModel()
}
```

Note que removemos a criação do *service* aqui, fizemos isso pois o `Koin` ficará responsável por injetar essa dependência, assim, aplicando o Princípio da Inversão da dependência.

Partiremos para a criação do *use case* que servirá de ponte entre o *repository* e a *ViewModel*. No momento ele será bem simples, terá apenas uma função `execute()` que acessará o `PokemonRepository` e retornará um `SinglePokemon`:

```kotlin
package br.com.pokedex.domain.interactor

import br.com.pokedex.domain.model.SinglePokemon
import br.com.pokedex.domain.repository.PokemonRepository

class GetSinglePokemonUseCase(private val repository: PokemonRepository) {

    suspend fun execute(id: Int) : SinglePokemon {
        return repository.getSinglePokemon(id)
    }
}
```

Modificação em `PokedexViewModel`:

```kotlin
package br.com.pokedex.presentation

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import br.com.pokedex.domain.interactor.GetSinglePokemonUseCase
import br.com.pokedex.domain.model.SinglePokemon
import br.com.pokedex.domain.repository.PokemonRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

private const val MIN_POKEMON_ID = 1
private const val MAX_POKEMON_ID = 151

class PokedexViewModel(private val useCase: GetSinglePokemonUseCase) : ViewModel() {

    private val _pokemon = MutableLiveData<List<SinglePokemon>>()
    val pokemon: LiveData<List<SinglePokemon>>
        get() = _pokemon

    fun getPokemon() {
        viewModelScope.launch(Dispatchers.IO) {
            val data = mutableListOf<SinglePokemon>()

            for (i in MIN_POKEMON_ID..MAX_POKEMON_ID) {
                data.add(useCase.execute(i))
            }

            withContext(Dispatchers.Main) {
                _pokemon.postValue(data.toList())
            }
        }
    }

}
```

Pronto, vamos começar a usar efetivamente o `Koin`. Inicialmente criaremos um *package* chamado `di`(abreviação para *dependency injection*), é nele que vamos construir nossos arquivos de injeção de dependências.  Após isso, criamos o arquivo `InfrastructureModule.kt`, e vamos criar algumas funções que provêem instâncias de dependências importantes nele: `Retrofit` e `PokemonApi`:

```kotlin
package br.com.pokedex.di

import br.com.pokedex.BuildConfig
import br.com.pokedex.data.api.PokemonApi
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

private fun providePokemonApi(retrofit: Retrofit): PokemonApi {
    return retrofit.create(PokemonApi::class.java)
}

private fun provideRetrofit(): Retrofit {
    return Retrofit.Builder().run {
        addConverterFactory(GsonConverterFactory.create())
        baseUrl(BuildConfig.POKE_API)
        build()
    }
}
```

Repare que em `provideRetrofit()` nós referenciamos uma string `POKE_API`, trata-se da url base da PokéAPI, fazemos isso para separá-la da lógica de negócios, nós a colocamos no arquivo `build.gradle` da seguinte forma:

```
def POKE_API = "POKE_API"
def URL_BASE_POKE_API = "\"https://pokeapi.co/api/v2/\""

android {
		...
    buildTypes {

        debug {
            applicationIdSuffix ".dev"
            debuggable true
            buildConfigField "String", POKE_API, URL_BASE_POKE_API
        }

    }
    ...
}
```

Neste momento em que já temos esses *providers*, podemos construir o módulo que irá prover essas dependências para a aplicação, vamos chamá-lo de `InfrastructureModule`, ele será uma coleção de dependências em que a instância de cada uma será retornada usando a função `factory` e depois injetada quando necessário for:

```kotlin
package br.com.pokedex.di

import br.com.pokedex.BuildConfig
import br.com.pokedex.data.api.PokemonApi
import br.com.pokedex.data.repository.PokemonRepositoryImpl
import br.com.pokedex.domain.repository.PokemonRepository
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

fun infrastructureModule() = module {
    factory { provideRetrofit() }
    factory { providePokemonApi(get()) }
    factory<PokemonRepository> { PokemonRepositoryImpl(get()) }
}

private fun providePokemonApi(retrofit: Retrofit): PokemonApi {
    return retrofit.create(PokemonApi::class.java)
}

private fun provideRetrofit(): Retrofit {
    return Retrofit.Builder().run {
        addConverterFactory(GsonConverterFactory.create())
        baseUrl(BuildConfig.POKE_API)
        build()
    }
}
```

Note que programamos o módulo de tal forma que quando uma instância de `PokemonRepository` for requisitada, retornaremos uma instância de `PokemonRepositoryImpl`, fazendo com que referenciemos apenas a abstração no código e não a implementação.

Faltam apenas duas dependências: `GetSinglePokemonUseCase`  e `PokedexViewModel`, procedemos com o desenvolvimento de outros dois arquivos: `DomainModule.kt`, para o *use case*, e `PresentationModule.kt`, para a *ViewModel*:

```kotlin
package br.com.pokedex.di

import br.com.pokedex.domain.interactor.GetSinglePokemonUseCase
import org.koin.dsl.module

fun domainModule() = module {
    factory { GetSinglePokemonUseCase(get()) }
}
```

```kotlin
package br.com.pokedex.di

import br.com.pokedex.presentation.PokedexViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

fun presentationModule() = module {
    viewModel { PokedexViewModel(get()) }
}
```

Sendo assim, já temos os três módulos prontos para serem injetados. Para realizar essa injeção, construiremos uma classe que será a raiz do nosso projeto, a `Pokedex`:

```kotlin
package br.com.pokedex

import android.app.Application

class Pokedex : Application() {}
```

Ela é subclasse de `Application` porque ela é a classe base do app que contém todos os outros componentes, como *activities* e *services*, ela é instanciada antes de qualquer outra classe quando o processo para a nossa aplicação é criado. E não podemos esquecer de adicioná-la ao `AndroidManifest`:

```xml
<?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=".Pokedex"
        ...
		>
        
        ...

    </application>

</manifest>
```

Agora vamos criar o arquivo que irá lidar com todas essas dependências, o `Injector.kt`,  nele nós desenvolvemos uma *extension function* de `Application` chamada `inject()` que inicia o `Koin` com todas as dependências necessárias:

```kotlin
package br.com.pokedex.di

import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin

fun Application.inject() {
    startKoin {
        androidLogger()
        androidContext(this@inject)
        modules(getModules())
    }
}

fun getModules() = listOf(
    infrastructureModule(),
    domainModule(),
    presentationModule()
)
```

Além de usarmos a função `startKoin`, também usamos `androidLogger()`, que fornece uma API simples para realizar logs relacionados ao `Koin`, `androidContext()`,  o qual adiciona uma instância de `Context` para o contêiner do `Koin`, `modules()`, para carregar as definições dos módulos, e `getModules()`, que retorna todos os módulos que nós criamos.

Pronto, já podemos inserir essea função na nossa classe `Application` dando um `override` na função `onCreate()`:

```kotlin
package br.com.pokedex

import android.app.Application
import br.com.pokedex.di.inject

class Pokedex : Application() {

    override fun onCreate() {
        super.onCreate()
        inject()
    }
}
```

Sendo assim, nosso projeto ficou estruturado dessa forma:


![Estrutura de pastas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/va3ofb81sm16x0y11d1k.png)

Após rodarmos o app, vamos perceber que nada mudou visualmente, mas sabemos que nosso projeto está bem melhor estruturado e desacoplado, obedecendo aos princípios da Arquitetura Limpa!

## Próximos posts

Nos próximos posts vamos melhorar a performance da nossa Pokédex usando a biblioteca Paging e a Flow API, além de deixá-las mais bonita, é claro.

Repositório no github:

{% embed https://github.com/ronaldocoding/pokedex %}

Post anterior:

{% embed https://dev.to/ronaldocoding/mostrando-os-pokemon-com-coil-em-android-35in  %}

Próximo post:

{% embed https://dev.to/ronaldocoding/melhorando-a-performance-da-pokedex-com-paging-3-e-flow-api-1l57 %}