Este documento define a arquitetura, as boas práticas de código e os princípios de desenvolvimento a serem seguidos no projeto Android, utilizando Kotlin e priorizando recursos estabelecidos da plataforma.
Adotaremos o padrão de arquitetura Model-View-ViewModel (MVVM) para garantir a separação de responsabilidades, testabilidade e manutenção do código.
| Camada | Tecnologias Chave (Kotlin/Android) | Responsabilidades |
|---|---|---|
| View (Activity/Fragment) | Activity, Fragment, Binding (ViewBinding ou DataBinding) | Observar o ViewModel, exibir o estado da UI, capturar eventos do usuário (clicks, inputs) e repassá-los ao ViewModel. Não contém lógica de negócios. |
| ViewModel | ViewModel, LiveData ou StateFlow/SharedFlow (para o estado da UI) | Manter o estado da UI de forma reativa e independente do ciclo de vida da View. Orquestrar a lógica de negócios, solicitando dados ao Repository. |
| Repository | Interfaces/Classes Repository, Coroutines (para operações assíncronas), Room (SQLite) | Abstrair as fontes de dados (local, remota). Gerenciar a lógica de cache, decidir de onde buscar os dados e mapear Entities para Models. |
| Data Sources | Room DAO, Retrofit Service | Implementação direta da comunicação com fontes de dados (ex: SQL Queries, chamadas HTTP). |
| Models | data class | Abstrações de domínio limpas e independentes de qualquer camada de persistência. |
A comunicação deve ser unidirecional e reativa, garantindo o baixo acoplamento:
- View -> ViewModel: A View (Activity/Fragment) chama funções públicas no ViewModel para indicar ações do usuário (ex: viewModel.loadUsers(), viewModel.onUserClick(id)).
- ViewModel -> View (Reativo): O ViewModel expõe o estado da UI através de LiveData ou StateFlow. A View observa esses dados e se atualiza automaticamente.
- ViewModel -> Repository: O ViewModel chama métodos de uma interface do Repository (ex: repository.getUsers()). O ViewModel não sabe como o dado é obtido.
- Repository -> Data Source: O Repository coordena as chamadas a um ou mais Data Sources (ex: localDataSource.queryAll(), remoteDataSource.fetchLatest()).
- Data Flow: O fluxo de dados deve ser claro:
- Entrada de Dados: Data Source (Entity) -> Repository (Mapeamento para Model) -> ViewModel -> View (Estado da UI).
- Saída de Dados (Persistência): View -> ViewModel (Model) -> Repository (Mapeamento para Entity) -> Data Source.
- Imutabilidade (Preferência): Use val sempre que possível. Prefira data class para Models e Entities.
- Funções de Escopo: Utilize let, run, apply, with e also para melhorar a legibilidade e gerenciar o escopo de objetos nulos ou com configuração.
- Tipos Nulos (?): Use o operador safe call (?.) e o operador elvis (?:) para lidar com nulidade de forma segura, evitando o operador de asserção não-nula (!!).
- Coleções: Use tipos de coleção imutáveis por padrão (ex: List<T>), convertendo para mutáveis (MutableList<T>) apenas onde necessário.
- Coroutines Tradicionais: Utilize suspend fun no Repository e ViewModel para operações assíncronas. Use viewModelScope no ViewModel e injete um CoroutineDispatcher no Repository para controle de threads (ex: Dispatchers.IO).
É crucial separar as abstrações de domínio da lógica de persistência:
-
Models (data class): Representam os objetos de domínio que a View e o ViewModel consomem. Eles não devem ter anotações de banco de dados (ex: @Entity do Room).
// Model usado pelo ViewModel e View
data class User(val id: Long, val name: String, val email: String) -
Entities (data class): Representam a estrutura da tabela no SQLite (Room).
// Entity usado pelo Room e DAO
@Entity(tableName = "user_table")
data class UserEntity(
@PrimaryKey val entityId: Long,
val entityName: String,
val entityEmail: String
) -
Mappers (Obrigatório): O Repository é responsável por mapear (converter) Entity para Model na leitura, e Model para Entity na escrita.
// Lógica dentro do Repository
fun UserEntity.toDomainModel(): User = User(id = this.entityId, name = this.entityName, email = this.entityEmail)
- YAGNI (You Ain't Gonna Need It - Você Não Vai Precisar Disso):
- Regra: Não adicione funcionalidade, bibliotecas ou complexidade que não sejam explicitamente necessárias para o requisito atual.
- Prática: Evite arquiteturas super-abrangentes no início. Adicione injeção de dependência (ex: Koin ou Hilt) somente quando a complexidade das dependências justificar.
- DRY (Don't Repeat Yourself - Não Se Repita):
- Regra: Qualquer pedaço de lógica ou informação deve ter uma única, inequívoca e autoritária representação dentro do sistema.
- Prática: Use funções e classes utilitárias para lógica comum (ex: formatação de datas, validação de inputs). Defina constantes em um único local.
- KISS (Keep It Simple, Stupid - Mantenha Simples, Estúpido):
- Regra: A simplicidade deve ser um objetivo chave no design.
- Prática: Prefira métodos pequenos e focados. Evite aninhamento profundo de condicionais (if/else ou when). Se um método tem mais de 20 linhas, ele provavelmente precisa ser refatorado.
- Nomeclatura Explícita: O nome da função deve descrever exatamente o que ela faz. Use verbos (ex: calculateTotal(), fetchUser(), formatDate()).
- Funções Focadas (SRP - Single Responsibility Principle): Cada função deve fazer apenas uma coisa e fazê-la bem.
- Argumentos Mínimos: Prefira funções com zero, um ou dois argumentos. Evite mais de três argumentos. Se precisar de muitos, agrupe-os em uma data class ou use argumentos nomeados.
- Retorno Único: Uma função deve ter um único ponto de saída (retorno). Use return no início apenas para guarda (early exit) de casos de erro ou pré-condição.
- Evitar Efeitos Colaterais: Funções devem, preferencialmente, ser puras (retornar o mesmo resultado para os mesmos inputs e não modificar estados externos). Se houver efeito colateral (ex: salvar no banco de dados), deixe-o explícito no nome da função (ex: saveChanges()).
.
├── ui/
│ ├── activity/
│ │ └── MainActivity.kt (View)
│ ├── viewmodel/
│ │ └── MainViewModel.kt (ViewModel)
│ └── adapter/
│ └── UserAdapter.kt
├── data/
│ ├── model/ (Abstrações de Domínio - Consumidas pelo VM)
│ │ └── User.kt
│ ├── repository/ (Interfaces de Contrato)
│ │ └── UserRepository.kt
│ ├── repository/impl/ (Implementação do Repositório)
│ │ └── UserRepositoryImpl.kt
│ ├── local/
│ │ ├── database/
│ │ │ └── AppDatabase.kt
│ │ ├── entity/ (Estrutura da Tabela - Usado pelo Room)
│ │ │ └── UserEntity.kt
│ │ └── dao/
│ │ └── UserDao.kt (Data Source Local)
│ └── remote/
│ └── service/
│ └── UserApiService.kt (Data Source Remoto)
└── util/
└── extensions/
└── Mappers.kt (Conversão entre Model e Entity)