Arquitetura Android
Stack técnico do app Android: linguagem, dependências, organização em camadas, padrões de rede e persistência usados pelas telas listadas em Visão Geral Android. O padrão canônico é o sufixo *New (v1); classes sem sufixo são legado (Retrofit/Elasticsearch, Client-Id/User-Id/Token) e não devem ser copiadas.
Stack
| Camada | Tecnologia | Observações |
|---|---|---|
| Linguagem | Kotlin | compileSdk 35 · minSdk 24 · targetSdk 35. |
| UI | XML Views + ViewBinding | _binding inflado em onCreateView, anulado em onDestroyView (evita leak). |
| Arquitetura | MVVM (ViewModel + Fragment por feature) | Ex.: SistemicListViewModel + FragmentSistemicList + adapter. Sem lógica de UI no ViewModel. |
| Navegação | Navigation Component (single-activity) | mobile_navigation.xml + AnimatedNavHost; safeargs habilitado. |
| Concorrência | Coroutines | viewModelScope.launch (nunca GlobalScope); IO em Dispatchers.IO, postValue/onComplete em Dispatchers.Main. |
| Mapa | OSMDroid (OpenStreetMap) | Implementação Android do módulo Mapa — equivalente ao MapKit no iOS. |
| Rede HTTP | OkHttp (cliente único compartilhado) | Timeouts 30s read/write/connect. Retrofit presente como dep. apenas no legado. |
| Realtime | Socket TCP (BackgroundService) | Protocolo MONIAPP do rastreador — ver Rastreador. |
| Push / Analytics | Firebase (FCM) — messaging/Message.kt | Token FCM enviado ao backend após login. |
| Gráficos | MPAndroidChart | Relatórios de temperatura; export PDF via iText. |
Camadas de uma feature típica (padrão *New)
Fragment
Infla o binding, lê o JWT de getSharedPreferences("jwt_tokens", …) e os args, dispara os fetches do ViewModel no onCreateView/onViewCreated e observa LiveData com viewLifecycleOwner. Strings sempre de R.string.*.
ViewModel
Estende ViewModel(), expõe LiveData imutável (backing field privado), tem companion object com BASE_URL/TAG. Funções públicas de fetch recebem jwt + ids + onComplete: (Boolean) -> Unit.
Rede (OkHttp)
get(url, jwt) com header Authorization: Bearer $jwt; getWithRetry(url, jwt, maxRetries=4) com backoff exponencial (1s, 3s, 9s…) só quando a mensagem contém 429. Sempre response.close() no finally.
Parse (processXxx)
Desempacota sempre o wrapper data (optJSONObject/optJSONArray), acesso defensivo com opt* + fallbacks, try/catch por item em loops. Chamadas paralelas com async + awaitAll para evitar 429.
Persistência
| Mecanismo | Uso |
|---|---|
SharedPreferences | JWT (jwt_tokens), empresa selecionada, perms_user, flags — equivalente ao UserDefaults do iOS. |
SharedPreferences + Gson | Cache offline de listas e da sessão do motorista (DriverSessionCache: blob por tripId_routeId). |
| BiometricPrompt / KeyStore | Login por biometria. |
build.gradle.kts, mas a persistência atual é feita com SharedPreferences + Gson — não há @Entity/@Dao em uso. Documentar conforme o código real.
Padrões de Rede v1
Authorization: Bearer <jwt> e usam base https://api.monisystem.com/v1. O wrapper { "data": ... } deve ser desempacotado em cada parser. Endpoints legados (api.monisat.online) ainda usam Client-Id / User-Id / Token — ver Autenticação.
private suspend fun getWithRetry(url: String, jwt: String, maxRetries: Int = 4): String {
var attempt = 0
while (true) {
try {
return get(url, jwt)
} catch (e: IOException) {
if (e.message?.contains("429") == true && attempt < maxRetries) {
delay(1000L * Math.pow(3.0, attempt.toDouble()).toLong()) // 1s, 3s, 9s…
attempt++
} else throw e
}
}
}
Padrão New vs Legado
| Aspecto | *New (canônico) | Legado |
|---|---|---|
| API | v1 — api.monisystem.com | api.monisat.online (Elasticsearch) |
| Auth | Bearer JWT | Client-Id / User-Id / Token |
| HTTP | OkHttp + Coroutines | Retrofit |
| Escopo | viewModelScope | GlobalScope (vaza corrotina) |
*New. DriverTripViewModelNew ainda usa GlobalScope — é dívida técnica, não referência.