Aula 26 · Semana 11 · Quarta, 18 de novembro de 2026 · 16:00 às 18:00
Observabilidade e review da sprint 2
Teoria (30 minutos): observabilidade em produção: relatórios de crashes, analytics, feature flags e remote config, métricas a acompanhar. Prática: review da sprint 2, retrospetiva e planeamento da sprint 3.
Sprint 3: 19/11 a 02/12
No fim desta aula consegues
Explicar o que um crash report contém e porque os símbolos e a versão da app são indispensáveis.
Comparar Crashlytics e Sentry e ligar os dois pontos de captura de erros do Flutter.
Definir eventos de analytics com sentido e dizer o que nunca se regista.
Usar feature flags e remote config para lançar desligado, desligar à distância e forçar atualização.
Apresentar a sprint 2 em 5 minutos e planear a sprint 3 com o que entra e o que fica de fora.
Sem observabilidade não sabes que a app está partida
Na web, o servidor tem logs e vês o erro em segundos. Em mobile, o código corre no telemóvel de um desconhecido, às vezes offline, numa versão que já não é a atual.
Um crash report traz: stack trace, versão e build number, modelo e versão do SO, memória livre, breadcrumbs (últimos ecrãs e ações) e se a app estava em primeiro plano.
Símbolos: em release o código é AOT e pode ser ofuscado; sem os ficheiros de símbolos (dSYM no iOS, mapping no Android, e os do Flutter com --split-debug-info) o stack trace é uma lista de endereços. Enviam-se no momento do build.
Agrupamento: o serviço junta crashes iguais numa issue, conta utilizadores afetados e diz "começou na 1.4.2". É isso que decide se fazes hotfix hoje ou na próxima versão.
Exemplo real: um crash só no Samsung A12 com Android 11 e o teclado da Samsung. Sem reports, nunca o reproduzias no teu telemóvel.
Para os testes: sem símbolos um crash report de release é ilegível; sem a versão da app não sabes se o bug ainda existe.
Analogia Crash reports e métricas são a caixa negra e o painel de instrumentos do avião. Sem eles, só se sabe que o avião caiu quando alguém telefona, e sem os dados do voo (versão, dispositivo, breadcrumbs) não se sabe porquê. A caixa negra regista o que aconteceu; o painel mostra, em voo, se está tudo dentro dos limites.
Crashlytics vs Sentry, e os dois pontos de captura
Firebase Crashlytics
Sentry
Custo
Grátis, dentro do Firebase
Grátis até 5 mil erros por mês; self-hosted possível
Integração
firebase_crashlytics, precisa do projeto Firebase e do google-services.json
sentry_flutter, só precisa de um DSN
Pontos fortes
Liga-se ao Analytics e ao Remote Config; deteta ANR no Android
Performance tracing, releases, breadcrumbs de navegação, backend na mesma conta
Erros não fatais
recordError
captureException
FlutterError.onError = (details) {
FlutterError.presentError(details); // continua a aparecer na consola
crash.registar(details.exception, details.stack); // erros de build, layout e paint
};
PlatformDispatcher.instance.onError = (error, stack) {
crash.registar(error, stack, fatal: true); // Futures sem catch, fora da árvore
return true; // tratado: a app não morre
};
Para os testes:FlutterError.onError apanha erros do framework; PlatformDispatcher.instance.onError apanha os assíncronos. Os dois SDKs ligam ambos por ti.
Analytics: eventos com sentido
Um evento é um nome e parâmetros: previsao_aberta {localidade: "horta", origem: "favoritos"}. Firebase Analytics, Mixpanel, Amplitude, PostHog.
Um evento com sentido responde a uma pergunta de produto: "quantos chegam ao detalhe?", "quantos adicionam um favorito na primeira sessão?". Se ninguém vai olhar para o gráfico, não registes.
Funil: uma sequência de eventos (abrir → lista → detalhe → favorito) e a percentagem que desiste em cada passo. É assim que se decide o que melhorar a seguir.
O que não registar: nome, email, localização exata, conteúdo de campos de texto, nada que identifique uma pessoa sem consentimento. RGPD, App Tracking Transparency no iOS e o data safety form do Play (aula 18).
O Spotify sabe em que segundo saltas a música. A tua app precisa de saber se alguém abre o mapa. Escala diferente, mesma lógica.
Para os testes: analytics regista eventos de produto, não dados pessoais; um funil mostra onde os utilizadores desistem.
Feature flags e remote config
Uma feature flag é um valor que a app lê no arranque e que decide se mostra a funcionalidade. Firebase Remote Config, LaunchDarkly, ou um endpoint teu /config.
Lançar desligado: o código do mapa vai na 1.5 com a flag a false. Ligas para 10% dos utilizadores, vês os crashes, ligas para todos. Sem nova submissão à loja.
Kill switch: a funcionalidade partida desliga-se em minutos; uma correção pela loja demora dias, e há quem nunca atualize.
A/B: metade vê o botão azul, metade o verde, e o analytics diz qual converte. Só faz sentido com volume; com 50 utilizadores é ruído.
Forced update: a config diz "versão mínima 1.4.0"; versões abaixo mostram um ecrã de bloqueio com link para a loja. Obrigatório quando mudas a API de forma incompatível (aula 28).
Regra: a app funciona com os valores por defeito se a config não chegar. O primeiro arranque pode ser offline.
Para os testes: uma feature flag liga ou desliga funcionalidades sem passar pela loja; o forced update bloqueia versões abaixo da mínima.
Analogia Uma feature flag é um interruptor no painel de instrumentos que se pode desligar em voo, sem aterrar. Uma correção pela loja é aterrar, reparar e voltar a descolar: demora dias, e há passageiros que nunca apanham o voo seguinte. O interruptor desliga a funcionalidade partida em minutos, na versão que já está instalada.
Métricas, alertas e logs
Métrica
O que é
Valor saudável
Crash-free users
Percentagem de utilizadores sem nenhum crash no período
Acima de 99,5%
ANR (Android)
"App Not Responding": a UI thread bloqueada 5 segundos
Abaixo de 0,47% das sessões, o limiar do Play
Tempo de arranque
Do toque no ícone ao primeiro frame útil
Abaixo de 2 s a frio (aula 30)
Adoção por versão
Percentagem de utilizadores em cada versão
Maioria na última ao fim de duas semanas
As lojas vigiam isto: o Play baixa a visibilidade de apps acima dos limiares (Android vitals); a App Store mostra crashes no Xcode Organizer.
Um alerta útil: "crash-free caiu abaixo de 99% na versão nova" → mensagem no Slack. Um alerta que ninguém lê é ruído.
Logs: dart:developer ou o package logging em vez de print; níveis; nunca tokens nem dados pessoais.
Para os testes: crash-free users e ANR são as métricas que as lojas vigiam; acima dos limiares, a visibilidade da app cai.
Review da sprint 2
5 minutos por grupo, com cronómetro. Demo no telemóvel projetado (scrcpy ou espelhamento), nunca no emulador.
Estrutura: 30 s o que prometeram na sprint; 3 min de demo; 1 min o que ficou por fazer; 30 s de perguntas.
Mostrar: a app a correr a partir de main, o modo avião ligado a meio, favoritos sincronizados entre dois telemóveis, o CI verde no README.
Quem assiste: cada grupo aponta uma coisa boa e faz uma pergunta ao grupo seguinte. O docente anota para a rubrica.
Se a demo falhar Diz o que devia acontecer e passa à frente. Ninguém é penalizado por um crash na demo; é penalizado por não o saber explicar.
Retro e planeamento da sprint 3
Retro (15 min)
Cada membro escreve três post-its: começar, parar, continuar.
O grupo escolhe um de cada e partilha com a turma em um minuto.
Tema típico: "parar de deixar PRs abertos três dias"; "começar a testar no telemóvel do colega".
Sprint 3, a última (19/11 a 02/12)
Entra: os requisitos mínimos em falta, no máximo um extra por grupo, testes e CI, o polish e a distribuição de sexta.
Fica de fora: funcionalidades novas sem os mínimos feitos, refactors grandes, trocar de arquitetura.
Pronto na sprint 3: instalável por um tester fora do grupo, README com screenshots, CI verde.
Backlog da sprint 3 em issues até sexta. Review da sprint 3 a 02/12; freeze a 09/12.
Demo do docente: Sentry num projeto Flutter em 5 minutos
Projeto no sentry.io do tipo Flutter; copia o DSN. Depois flutter pub add sentry_flutter.