Aula 33 · Semana 13 · Sexta, 4 de dezembro de 2026 · 08:30 às 12:30
Revisão para o teste 2 e workshop de finalização
Primeira parte: revisão da matéria das aulas 21 a 32, com dez perguntas-tipo resolvidas em conjunto e os erros mais frequentes. Segunda parte: finalização do projeto: checklist de entrega, correções, README, build final assinado e upload aos testers. O teste é na segunda-feira seguinte; o freeze na quarta.
No fim desta aula consegues
Situar cada tema das aulas 21 a 32 e dizer qual é a ideia central que o teste vai perguntar.
Resolver perguntas de escolha múltipla por eliminação, identificando a opção "quase certa" que é a armadilha.
Reconhecer os cinco erros clássicos desta matéria e explicar porque estão errados.
Verificar o projeto contra a checklist de entrega e fechar o que falta.
Gerar o build final assinado e distribuí-lo aos testers pelo Firebase App Distribution ou TestFlight.
Mapa da matéria (1 de 2): aulas 21 a 26
Aula
Tema
A ideia que o teste pergunta
21
Localização e mapas
Precisão vs bateria; when-in-use vs always; negação permanente; OSM grátis vs Google Maps com chave e custos.
22
Deep links
Custom scheme vs Universal/App Links; verificação de domínio com AASA e assetlinks; o router decide o ecrã.
23
Background
O SO limita por bateria; WorkManager com restrições e sem hora exata; iOS sem garantias; silent push.
24
Hardware e sensores
Cada sensor tem permissão, consumo e limites por plataforma; NFC mais restrito no iOS; BLE precisa de permissões de scan.
25 e 26
Testes, CI e observabilidade
Pirâmide; fakes em vez de rede; CI como definição de pronto; crash-free, ANR, feature flags e kill switch.
Mapa da matéria (2 de 2): aulas 27 a 32
Aula
Tema
A ideia que o teste pergunta
27
Distribuição a testers
App Distribution sem revisão; TestFlight externo com revisão beta; internal testing até 100; nunca distribuir debug.
28
Platform channels e APIs
MethodChannel e tipos do codec; avaliar um package; versionar uma API sem partir apps instaladas.
29
Lojas e monetização
IAP obrigatório para conteúdo digital; motivos de rejeição; ASO e pedir avaliação no momento certo.
30
Tamanho, arranque e atualizações
AAB e split por ABI; medir antes do primeiro frame; code push com limites; forced update.
31 e 32
Build, assinatura e consolas
Assinatura prova autoria e integridade; versionCode nunca desce; tracks, rollout gradual, data safety e privacy labels, vitals.
Como responder a escolha múltipla
Lê a pergunta até ao fim antes das opções. Sublinha a palavra que muda tudo: sempre, nunca, garante, obrigatório, só.
Elimina primeiro as duas que são claramente de outro tema ou absolutas demais. Ficam duas: uma certa e uma "quase certa".
A "quase certa" costuma ser verdadeira noutro contexto (Android em vez de iOS, debug em vez de release, web em vez de mobile). Pergunta-te "para esta plataforma e este cenário?".
Não há penalização por errar: responde a todas. Uma pergunta que não sabes ao fim de 90 segundos, marca e volta no fim.
40 perguntas em 75 minutos dá menos de dois minutos por pergunta. O tempo não é o problema se não empatares.
Nos próximos dez slides o formato é o do teste. Votas, e só depois vemos a resposta.
Pergunta 1 · Localização
A Tempo Açores quer abrir na localidade mais próxima do utilizador quando ele abre a app. Que permissão deve pedir?
Localização always, para funcionar também em segundo plano.
Localização when in use, com precisão aproximada a chegar.
Localização em segundo plano com um foreground service para não falhar.
Nenhuma: as coordenadas do GPS são públicas e não precisam de permissão.
Pergunta 2 · Deep links
Porque é que um link de "login mágico" enviado por e-mail não deve usar um custom scheme como tempoacores://login?token=...?
Porque os custom schemes não funcionam no Android.
Porque qualquer app instalada pode registar o mesmo scheme e receber o link com o token.
Porque o iOS só permite schemes registados na App Store.
Porque os custom schemes exigem HTTPS e um certificado.
Pergunta 3 · Background
Configuras o WorkManager para atualizar a cache de previsões de 6 em 6 horas. O que o Android garante?
Que a tarefa corre exatamente de 6 em 6 horas, mesmo em Doze.
Que a tarefa corre pelo menos uma vez em cada período, quando as restrições permitirem, e sobrevive a reinícios.
Que a tarefa corre só enquanto a app estiver em primeiro plano.
Que a mesma configuração funciona no iOS com a mesma garantia.
Pergunta 4 · Hardware
Uma app quer fazer scan de dispositivos Bluetooth Low Energy num Android atual. O que precisa?
Nada: o scan BLE é livre porque não identifica o utilizador.
Só a permissão de NFC, porque BLE e NFC partilham o rádio.
A permissão BLUETOOTH_SCAN (Android 12 e posteriores) e, em versões anteriores, a permissão de localização.
A permissão de câmara, porque o scan usa o leitor de códigos.
Pergunta 5 · Testes
Na pirâmide de testes há muitos testes unitários e poucos de integração ou E2E. Porquê?
Porque os unitários são rápidos, baratos e apontam diretamente a causa da falha.
Porque o Flutter não suporta testes de integração.
Porque os unitários testam a interface e os E2E só testam a lógica.
Porque os golden tests substituem os testes E2E.
Pergunta 6 · Observabilidade
O Crashlytics mostra "crash-free users: 99,2 %" na última semana. O que isto significa?
Que 0,8 % das sessões terminaram num crash.
Que 99,2 % dos crashes já foram corrigidos.
Que 0,8 % dos utilizadores tiveram pelo menos um crash nesse período.
Que a app tem 0,8 % de código com bugs.
Pergunta 7 · Distribuição a testers
Qual é a diferença essencial entre distribuir um build a testers externos pelo TestFlight e pelo Firebase App Distribution?
O Firebase App Distribution só funciona para iOS.
O TestFlight externo passa por uma revisão beta da Apple; o Firebase entrega o ficheiro sem revisão.
O TestFlight não tem limite de testers; o Firebase está limitado a 25.
Não há diferença: ambos instalam através da App Store.
Pergunta 8 · Platform channels
Que tipos podes enviar diretamente por um MethodChannel entre Dart e Kotlin ou Swift?
Qualquer objeto Dart, porque o canal serializa automaticamente com reflexão.
Só String; tudo o resto tem de ir em JSON.
Primitivos, String, listas e mapas suportados pelo StandardMessageCodec; objetos próprios convertem-se em mapas.
Widgets, para que o lado nativo os possa desenhar.
Pergunta 9 · Lojas e monetização
A Tempo Açores passa a vender uma subscrição "Previsão a 10 dias" dentro da app iOS. O que a Apple exige?
Nada: a app pode aceitar cartão com qualquer fornecedor de pagamentos.
Que a compra de conteúdo digital use o sistema de compras in-app da Apple, com a comissão respetiva.
Que a subscrição seja vendida só no site e a app nunca a mencione.
Que a app seja paga na loja em vez de ter subscrição.
Pergunta 10 · Build e versionamento
Carregaste um app bundle com versionCode 7 para o track de teste interno. Corriges um bug e queres carregar para produção. O que o novo bundle precisa?
Nada: os tracks são independentes e o 7 pode repetir-se.
versionCode 7 e um versionName diferente.
versionCode 8 ou superior; o versionName pode ser o mesmo.
Um keystore novo, porque a chave de teste não serve para produção.
Erros frequentes no teste
O erro
Porque está errado
Pedir localização always "para garantir"
A app não precisa, as lojas rejeitam sem justificação e o utilizador nega. When in use chega para quase tudo.
Tratar um custom scheme como seguro
Não tem dono nem verificação de domínio. Para tokens e login, Universal/App Links.
Contar com o WorkManager a uma hora certa
Garante execução dentro de restrições, não hora. O SO junta e adia trabalho por bateria.
Achar que code push muda tudo sem loja
Só código Dart; nada de nativo, assets ou permissões. E a Apple exige que não mude o propósito da app.
Reutilizar o versionCode
A Play recusa o upload e o telemóvel recusa o downgrade. Sobe sempre; o versionName é que pode repetir.
Para os testes: quando uma opção promete uma garantia absoluta (sempre, exatamente, sem limites) num sistema móvel, desconfia.
Workshop de finalização: checklist de entrega
Requisitos mínimos a funcionar no telemóvel: localidades com pesquisa, previsão a 5 dias, avisos, favoritos sincronizados, funcionamento sem rede com "dados de há X", estados de loading, erro e vazio em todos os ecrãs.
Modo caos ligado durante 5 minutos sem a app falhar.
CI verde no main: flutter analyze sem avisos e pelo menos cinco testes a passar.
Sem segredos no repositório:git log -p | grep -i secret limpo, key.properties e keystore fora.
README completo (slide seguinte).
Build assinado1.0.0+N, distribuído pelo Firebase App Distribution a pelo menos 3 testers fora do grupo, e pelo TestFlight quem tiver iOS.
Como vai correr 10:35 a 10:50: cada grupo percorre a checklist e marca o que falta na milestone. 10:50 a 12:00: trabalho, com o docente a rodar. 12:00 a 12:30: uploads e verificação cruzada com outro grupo.
README e documentação: o que tem de lá estar
Cabeçalho: nome da app, os membros com o utilizador GitHub, e um screenshot ou GIF da app a correr no telemóvel.
Como correr: versão do Flutter, flutter pub get, o que pôr no --dart-define ou no env/dev.json, e como obter o client-id (não o próprio segredo).
Arquitetura em dez linhas: camadas, pastas por feature, onde vive o estado, como a cache funciona. Um diagrama simples vale mais do que dez parágrafos.
O que está feito e o que não está: os mínimos, os extras, a milestone "Depois". Honestidade aqui é avaliada; esconder um bug conhecido não é.
Como obter o build: link de convite do App Distribution, versão atual, e quem tem o keystore.
Retrospetiva: as duas frases da aula 32.
Exercício Troca o README com outro grupo. Sem falar com eles, tenta correr a app só com o que lá está escrito. Cada coisa que tiveste de perguntar é uma linha que falta.
Build final assinado e upload ao App Distribution
Sobe o build number no pubspec.yaml: version: 1.0.0+N, com N maior do que o último que distribuíste.
Gera o APK de release com o flavor prod e a configuração de prod.
Faz o upload com o CLI do Firebase, para o grupo de testers da turma que criaste na aula 27.
firebase appdistribution:distribute \
build/app/outputs/flutter-apk/app-prod-release.apk \
--app 1:123456789012:android:0123456789abcdef \
--groups turma-pdm \
--release-notes "v1.0.0 (N): build final para a defesa"
Confirma no telemóvel de outro grupo que a notificação do App Distribution chegou e que a app instala por cima da anterior, sem desinstalar.
Exercício Build distribuído a pelo menos 3 testers fora do grupo, com o link do release colado no README. Se a instalação por cima falhar, a causa está na aula 31: chave ou versionCode.
TestFlight, para quem tem iOS
Conta paga e Mac. O docente faz o upload com a conta dele para os grupos que tenham a app a compilar em iOS; o bundle id tem de ser o combinado com ele.
Gera o ficheiro .ipa e abre-o no Xcode ou no Transporter.
flutter build ipa --release --flavor prod --dart-define-from-file=env/prod.json
open build/ios/archive/Runner.xcarchive
Distribute App → App Store Connect → Upload. O build aparece em "Processing" durante alguns minutos.
Testers internos não precisam de revisão: adiciona os Apple IDs dos colegas ao grupo de teste interno, e eles recebem o convite na app TestFlight.
Sem Mac: não há caminho. Fica no README que o build iOS não foi distribuído e porquê; não é penalizado.
Até à próxima aula
Para fazer
Estudar a componente teórica das aulas 21 a 32. O teste é segunda, 07/12, às 16:00, em papel e sem consulta.
Fechar o que ficou por marcar na checklist de hoje. O freeze é quarta às 18:30, com tag v1.0.0.
Confirmar que os 3 testers fora do grupo instalaram mesmo o build.
Para ler
Request location permissions (developer.android.com): foreground vs background, precise vs approximate, com as regras por versão (aula 21).
Deep linking (docs.flutter.dev): custom schemes, App Links e Universal Links, e a verificação de domínio com assetlinks.json e AASA (aula 22).
WorkManager (developer.android.com): o que o sistema garante e o que não garante a uma tarefa em background (aula 23).
Build and release an Android app (docs.flutter.dev): assinatura, app bundle e versionamento; revê os passos se algum upload falhou hoje (aula 31).