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. Essa série também é para você que quer aprender do zero.
A maior motivação para escrever essa série de artigos é fazer com que mais desenvolvedores possam ficar atualizados com esse “novo” approach, que vem sendo bastante utilizado no desenvolvimento Android .
Inicialmente, vamos ter três artigos na seguinte ordem:
- Domain
- Data
- Presentation
O que preciso para seguir os artigos: Vontade de aprender!
Domain Module:
Vamos começar pela 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 da 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, sendo que 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: é o interface de comunicação, que solicita dados, do backend ou cache.
Diagrama de fluxo do module domain.
A 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 o dado para a presentation.
Agora vamos começar a desenvolver esse modulo passo a passo:
- Começar um projeto novo
- Selecionar Empty Acitivity
- Colocar o nome que você quiser no projeto(usei MyModuleExample)
- Usar Android X
- E coloque o minSdk = 20
Temos nosso projeto criado, agora vamos criar a nossa domain:
A primeira coisa que vamos fazer talvez não seja algo da domain em especifico, mas é necessária ser feita logo no início do desenvolvimento do projeto.
Vamos criar um arquivo de dependências, onde vamos centralizar as gradledependencies 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 da nossa domain. Mãos à obra:
- Crie o arquivo na raiz do projeto e coloque o nome de dependencies.gradle.
- Dentro desse 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 ja podemos utilizar no nosso projeto. Primeiro vamos configurar no build.gradle do projeto:
- Configurar: apply from: ‘dependencies.gladle’ dentro do buildScript
- Setar versões: o interessante é que quando vamos utilizar 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 da nossa domain(FINALMENTE!!)
Vamos ao build.greadle da 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 gist abaixo.
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
Mas qual o motivo de criar isso tudo para um projeto simples?
Sendo simples ou não, o projeto vai 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 vão ter 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 DA DOMAIN!
Obs: nosso projeto mostrara uma lista de jobs Android e vamos utilizar a seguinte api: http://demo8470178.mockable.io/android-jobs
Vamos ter 4 pacotes na domain:
- Entities: é onde vamos criar nossas data classes. E a primeira que vamos criar 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 use case 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 recebera um modulekoin, e dentro desse module as dependências que vamos prover estão. Utilizei o factory pois quero 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 ja foi criada e só vamos pegar ela para utilizar no UseCase e para o scheduler = Schedulers.io()passamos o Scheduler que queremos utilzar, e 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 da 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.
Domain repo : https://github.com/ifucolo/android-modularization/tree/domain
follow me: https://twitter.com/Iagolfucolo
Revisores: