API v1

Arquitetura Android

Kotlin · MVVM · ViewModel + Fragment

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

CamadaTecnologiaObservações
LinguagemKotlincompileSdk 35 · minSdk 24 · targetSdk 35.
UIXML Views + ViewBinding_binding inflado em onCreateView, anulado em onDestroyView (evita leak).
ArquiteturaMVVM (ViewModel + Fragment por feature)Ex.: SistemicListViewModel + FragmentSistemicList + adapter. Sem lógica de UI no ViewModel.
NavegaçãoNavigation Component (single-activity)mobile_navigation.xml + AnimatedNavHost; safeargs habilitado.
ConcorrênciaCoroutinesviewModelScope.launch (nunca GlobalScope); IO em Dispatchers.IO, postValue/onComplete em Dispatchers.Main.
MapaOSMDroid (OpenStreetMap)Implementação Android do módulo Mapa — equivalente ao MapKit no iOS.
Rede HTTPOkHttp (cliente único compartilhado)Timeouts 30s read/write/connect. Retrofit presente como dep. apenas no legado.
RealtimeSocket TCP (BackgroundService)Protocolo MONIAPP do rastreador — ver Rastreador.
Push / AnalyticsFirebase (FCM) — messaging/Message.ktToken FCM enviado ao backend após login.
GráficosMPAndroidChartRelatórios de temperatura; export PDF via iText.

Camadas de uma feature típica (padrão *New)

1

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.*.

2

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.

3

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.

4

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

MecanismoUso
SharedPreferencesJWT (jwt_tokens), empresa selecionada, perms_user, flags — equivalente ao UserDefaults do iOS.
SharedPreferences + GsonCache offline de listas e da sessão do motorista (DriverSessionCache: blob por tripId_routeId).
BiometricPrompt / KeyStoreLogin por biometria.
Room está declarado no 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

Todas as chamadas v1 exigem header 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
APIv1 — api.monisystem.comapi.monisat.online (Elasticsearch)
AuthBearer JWTClient-Id / User-Id / Token
HTTPOkHttp + CoroutinesRetrofit
EscopoviewModelScopeGlobalScope (vaza corrotina)
Ao criar/editar tela nova, seguir SEMPRE o padrão *New. DriverTripViewModelNew ainda usa GlobalScope — é dívida técnica, não referência.