MVVM, Koin, Rx, Room, Databinding e um pouco mais…

Certamente você já ouviu falar de modularização em algum momento no desenvolvimento Android, certo? Se não, sem problemas. Esta série também é para você que quer aprender do zero.

A maior motivação para escrever esta série de artigos é fazer com que mais pessoas desenvolvedoras possam ficar atualizadas com esse “novo” approach, que vem sendo bastante utilizado no desenvolvimento Android.

Inicialmente, teremos três artigos, na seguinte ordem:

  • Domain
  • Data
  • Presentation

O que é preciso para seguir os artigos? Vontade de aprender!


Domain Module

Vamos começar pelo Domain, que é responsável pela comunicação com o Presentation module e, também, é um module kotlin/java puro, ou seja, não possui dependências Android.

Conteúdo do domain:

  • Entidades: são nossas entidades que possuem somente os dados que queremos enviar para o ViewModel/Presenter, ou seja, o dado mapeado do backend.
  • UseCases: é onde teremos nossas regras de negócios com base nos dados que são solicitados do repository. Digamos que ele é “blindado”: mudanças feitas aqui não devem afetar outros módulos do projeto, assim como mudanças em outros módulos não devem refletir no UseCase.
  • Repository: é a interface de comunicação, que solicita dados, do backend ou cache.

Diagrama de fluxo do module Domain.

O presentation solicita algum dado para UseCase, que pede para o repository, que vai buscar onde ele está implementado. O dado vem para o UseCase, o qual pode aplicar alguma regra de negócio e devolve para o presentation.

Agora, começaremos a desenvolver esse modulo passo a passo:

  • Comece um projeto novo
  • Selecione Empty Acitivity
  • Coloque o nome que você quiser no projeto (eu usei MyModuleExample)
  • Selecione Android X
  • E defina minSdk para 20

Temos nosso projeto criado, agora vamos criar o nosso domain:

A primeira coisa que faremos talvez não seja algo do domain em especifico, mas é necessário que seja feita logo no início do desenvolvimento do projeto.

Vamos criar um arquivo de dependências, onde centralizaremos as gradle dependencies do nosso projeto como libs, suas versões e algumas coisas mais. Ao longo do projeto, vamos evoluindo o arquivo de acordo com a necessidade. No primeiro momento vamos criar o arquivo, configurar o build.gradle do projeto com ele, e setar as dependências do nosso domain. Mãos à obra:

  • Crie o arquivo na raiz do projeto e coloque o nome de dependencies.gradle.
  • Dentro deste arquivo, coloque as versões das libs e um array de dependências com suas respectivas versões nomeadas anteriormente.
ext {

    kotlinVersion = '1.3.21'

    buildTools = '3.4.0-beta04'

    //Rx
    rxJavaVersion = '2.2.7'
    rxKotlinVersion = '2.2.0'
    rxAndroidVersion = '2.1.1'

    //Koin
    koinVersion = '1.0.2'

    dependencies = [
        kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion",
        rxJava: "io.reactivex.rxjava2:rxjava:$rxJavaVersion",
        rxKotlin: "io.reactivex.rxjava2:rxkotlin:$rxKotlinVersion",
        rxAndroid: "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion",
        koin: "org.koin:koin-android:$koinVersion",
    ]
}

Agora já podemos utilizá-lo em nosso projeto. Primeiro, vamos configurar no build.gradle do projeto:

  • Configurar: apply from: ‘dependencies.gradle’ dentro do buildScript
  • Setar versões: o interessante é que quando utilizamos esse approach temos que utilizar aspas duplas na nossa dependência e, assim, setar a versão com o $kotlinVersion.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {

    apply from: 'dependencies.gradle'

    repositories {
        google()
        jcenter()

    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
        classpath "com.android.tools.build:gradle:$buildTools"
    }
}

allprojects {
    repositories {
        google()
        jcenter()

    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Agora, podemos configurar as dependências do nosso domain (FINALMENTE!)

apply plugin: 'java-library'
apply plugin: 'kotlin'

dependencies {

    def dependencies = rootProject.ext.dependencies

    implementation dependencies.kotlin
    implementation dependencies.rxJava
    implementation dependencies.koin
}

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

Vamos ao build.gradle do domain e vamos criar uma variável dependencies que capta todas as dependências do arquivo que criamos anteriormente e, com isso, podemos utilizar as que queremos implementar como no código acima.

Mas qual o motivo de criar isso tudo para um projeto simples?

Sendo simples ou não, o projeto terá todas as dependências centralizadas em apenas um lugar, ou seja, se outros módulos utilizam RxJava por exemplo, com esse approach garantimos que todos os módulos terão a mesma versão da lib. Com isso, evitamos de ter um módulo com versões diferentes de outros e, quando vamos atualizar para versões mais novas, todos os módulos são atualizados.

Então, não importa se o projeto é simples, sempre temos que pensar em evitar retrabalho no futuro e tentar montar nossa estrutura o mais clean possível.

AGORA, VAMOS PARA DENTRO DO DOMAIN!

Obs: nosso projeto mostrará uma lista de jobs Android e utilizaremos a seguinte api: http://demo8470178.mockable.io/android-jobs

Vamos ter 4 pacotes no domain:

  • Entities: é onde vamos criar nossas data classes. E a primeira que criaremos será AndroidJob:
data class AndroidJob(
    val title: String,
    val experienceTimeRequired: Int,
    val native: Boolean,
    val country: String
)
  • Repository: aqui ficam nossas interfaces de comunicação com o module data (próximo artigo), e segue nosso primeiro repository, que vai retornar uma lista observável de AndroidJobs:
interface AndroidJobsRepository {
    fun getJobs(forceUpdate: Boolean): Observable<List<AndroidJob>>
}
  • UseCases: nossos casos de uso que serão solicitados pela camada de apresentação. E nosso primeiro UseCase será para pegar a lista de AndroidJobs:
class GetJobsUseCases(
    private val repository: AndroidJobsRepository,
    private val scheduler: Scheduler
) {

    fun execute(forceUpdate: Boolean): Observable<List<AndroidJob>> {
        return repository.getJobs(forceUpdate)
            .subscribeOn(scheduler)
    }
}

Podemos verificar no construtor que temos duas dependências necessárias para utilizar o UseCase, o repository de onde vamos solicitar a lista de dados, e um scheduler para informar a thread onde vamos assinar a nossa chamada. Essas duas dependências serão entregues utilizando injeção de dependência através do framework koin.

  • Di: apenas um pacote para centralizar as dependências desse module:
val useCaseModule = module {

    factory {
        GetJobsUseCases(
            repository = get(),
            scheduler = Schedulers.io()
        )
    }
}

val domainModule = listOf(useCaseModule)

Criamos uma variável chamada useCaseModule que receberá um module koin e, dentro desse module, estão as dependências que vamos prover. Utilizei o factory, pois queremos que crie uma nova instância toda vez que for requerida essa dependência.

Temos repository = get(), onde o get significa que em algum lugar do projeto essa dependência já foi criada e só vamos pegá-la para utilizar no UseCase. E, para o scheduler = Schedulers.io(), passamos o Scheduler que queremos utilzar. Nesse caso, foi o IO mas poderia ser computation etc. (https://stackoverflow.com/questions/33370339/what-is-the-difference-between-schedulers-io-and-schedulers-computation)

E, por fim, temos algo assim:

O nosso projeto ainda não faz nada, mas estamos construindo passo a passo. Por essa razão, agora o nosso prêmio é se contentar apenas com a criação do domain.

O ideal seria aplicar testes já nessa etapa, mas primeiro vamos entender os conceitos de modularização e, quando terminarmos, vamos dar start à implementação dos testes.

Você pode conferir o código completo nesse repositório no GitHub.