Aula 24 · Semana 10 · Quarta, 11 de novembro de 2026 · 16:00 às 18:00
Hardware e sensores, e sprint 2
Teoria (30 minutos): acesso ao hardware: câmara, sensores de movimento, NFC, Bluetooth Low Energy, biometria e haptics; permissões, consumo e diferenças entre iOS e Android. Prática: sprint 2, a uma semana da review.
Review da sprint 2: quarta 18/11
No fim desta aula consegues
Escolher o package certo para câmara, sensores, NFC, BLE e biometria, e dizer que permissão cada um exige em Android e iOS.
Explicar a diferença entre ler uma tag NDEF e emular um cartão (HCE), e porque o iOS é mais restritivo.
Distinguir o papel de central e de periférico em BLE e justificar porque o Android pede localização para fazer scan.
Usar haptics e biometria como detalhes de qualidade, não como funcionalidades centrais.
Fechar o ponto de situação da sprint 2 com a demo da review já planeada.
Câmara e galeria
image_picker abre a câmara ou a galeria do sistema e devolve um ficheiro. Chega para a maioria dos casos: foto de perfil, anexar uma imagem a um formulário. É o que o WhatsApp faz quando anexas uma foto.
camera dá o preview ao vivo dentro da app, controlo de foco, flash e vídeo. Precisas dele para ler códigos QR ou aplicar filtros. Custa mais código e mais permissões.
Permissões: Android 13+ usa o Photo Picker e não pede permissão para escolher da galeria; a câmara exige CAMERA. iOS exige NSCameraUsageDescription e NSPhotoLibraryUsageDescription no Info.plist, com texto de justificação, ou a app crasha.
Tamanho: uma foto de 12 MP tem 4 a 6 MB. Antes de enviar, reduz com maxWidth e imageQuality do image_picker ou com flutter_image_compress. Um upload de 6 MB em 3G falha; um de 300 KB não.
Para os testes:image_picker delega no sistema e não tem preview; camera tem preview e custa mais permissões e código.
Sensores de movimento
Sensor
Mede
Usos reais
Acelerómetro
Aceleração em x, y, z, incluindo a gravidade
Detetar abanão, orientação do ecrã, contar passos de forma grosseira
Giroscópio
Velocidade angular
Rotação fina, realidade aumentada, jogos
Magnetómetro
Campo magnético
A seta da bússola no Google Maps
Barómetro
Pressão atmosférica
Altitude relativa, andares subidos; podia dar a pressão local no Tempo Açores
sensors_plus expõe cada sensor como um Stream: accelerometerEventStream(), gyroscopeEventStream(), magnetometerEventStream(), barometerEventStream(). Sem permissões em nenhuma plataforma.
Um stream a 100 Hz mantém o CPU acordado. Escolhe o samplingPeriod, cancela a subscrição no dispose e quando a app vai para segundo plano.
Para os testes: os sensores de movimento não pedem permissão, mas custam bateria; cancela o stream quando ninguém está a olhar.
Analogia Os sensores são os sentidos do telemóvel. O acelerómetro e o giroscópio são o ouvido interno: dão o equilíbrio e o movimento sem pedir licença a ninguém. A câmara é a vista. O NFC é o toque: só funciona a centímetros e num instante. O BLE é a voz: alcança dezenas de metros e mantém uma conversa. Cada sentido tem a sua permissão e o seu custo em bateria.
NFC
Tags NDEF são a linguagem comum das etiquetas: texto, URL, vCard, configuração de Wi-Fi. Leitura e escrita com nfc_manager. Exemplos: etiquetas em museus, cartões de visita, autocolantes que abrem uma app.
Android: lê e escreve tags em segundo plano, sem ecrã do sistema; precisa de android.permission.NFC no manifesto, sem prompt ao utilizador. Faz HCE (Host Card Emulation): o telemóvel finge ser um cartão, com um serviço nativo.
iOS: lê desde o iOS 11 e escreve desde o 13, mas sempre numa sessão com a folha do sistema visível ("Ready to Scan"); precisa de NFCReaderUsageDescription e da capability no Xcode. HCE só desde o iOS 17.4, só na UE e com autorização da Apple.
Pagamentos (Apple Pay, Google Wallet) usam NFC com Secure Element. Uma app normal não imita isso; um "cartão de transportes" caseiro não é possível.
Para os testes: em NFC, o Android faz leitura, escrita e HCE; o iOS lê e escreve numa sessão do sistema e HCE é caso especial.
Bluetooth Low Energy
Central é quem procura e liga (o telemóvel). Periférico é quem anuncia (a pulseira, o sensor de temperatura, a fechadura). O telemóvel também pode ser periférico, mas raramente compensa.
Fluxo: scan de anúncios → ligar → descobrir serviços → ler, escrever ou subscrever características. Cada característica é um UUID com bytes; o significado vem do standard (Heart Rate Service 0x180D) ou do fabricante.
flutter_blue_plus: scan com filtros, ligação, read(), write() e lastValueStream por característica.
Permissões: até ao Android 11 o scan exige permissão de localização, porque beacons revelam onde estás. Android 12+ tem BLUETOOTH_SCAN e BLUETOOTH_CONNECT, com a flag neverForLocation. iOS pede NSBluetoothAlwaysUsageDescription.
Exemplos: Mi Band, estações meteorológicas domésticas, AirTags, o sistema de rastreio de contactos da pandemia.
Para os testes: BLE em Android antigo pede localização para o scan; não é um bug, é privacidade.
Biometria, haptics, widgets de ecrã inicial
local_auth: Face ID, Touch ID, impressão digital, com authenticate(localizedReason: ...). Desbloqueia algo local: mostrar o token guardado, entrar num ecrã sensível. Não substitui a autenticação no servidor (aula 17).
Haptics:HapticFeedback.lightImpact(), mediumImpact(), heavyImpact(), selectionClick(). Sem permissão, sem package, e é dos detalhes que mais fazem uma app parecer nativa. O iOS tem o motor Taptic; no Android depende do hardware.
Widgets de ecrã inicial:home_widget faz a ponte com App Widgets (Android) e WidgetKit (iOS). A UI do widget é nativa; o Flutter só escreve os dados. Caso perfeito: a previsão de hoje da localidade favorita.
Live Activities (iOS 16.1+) e Dynamic Island em uma frase: uma notificação viva cujos dados atualizas; código Swift obrigatório. Exemplos: entregas, voos, resultados desportivos.
Para os testes: biometria é desbloqueio local; um widget de ecrã inicial é desenhado em código nativo e o Flutter só lhe dá dados.
Sprint 2: ponto de situação
Cada grupo, um minuto: o que está em main, o que está em PR, o que está bloqueado.
A review é na quarta 18/11. O que não estiver em main até terça à noite não entra na demo.
Prioridade desta semana: fechar funcionalidades, não começar novas. Estados de erro, modo avião, favoritos sincronizados.
O docente circula pelos grupos. Bloqueios resolvem-se ao vivo, no vosso código, não em abstrato.
Regra da última semana Um PR pequeno merged vale mais do que uma funcionalidade grande a meio. Se uma issue não cabe até terça, parte-a ou move-a para a sprint 3.
Demo: ler uma tag NFC e vibrar
No telemóvel do docente. O código é curto, e o que interessa é a sequência: verificar disponibilidade, abrir sessão, tratar a tag, fechar sessão.
import 'package:flutter/services.dart';
import 'package:nfc_manager/nfc_manager.dart';
import 'package:nfc_manager_ndef/nfc_manager_ndef.dart';
class NfcService {
Future<void> lerTag({required void Function(String) aoLer}) async {
if (!await NfcManager.instance.isAvailable()) {
throw StateError('NFC indisponível ou desligado neste telemóvel');
}
await NfcManager.instance.startSession(
pollingOptions: {NfcPollingOption.iso14443, NfcPollingOption.iso15693},
onDiscovered: (NfcTag tag) async {
final mensagem = await Ndef.from(tag)?.read();
final registo = mensagem?.records.firstOrNull;
final texto = registo == null
? 'tag sem conteúdo NDEF'
: String.fromCharCodes(registo.payload.skip(3)); // salta o cabeçalho do Text Record
await HapticFeedback.heavyImpact();
aoLer(texto);
await NfcManager.instance.stopSession();
},
);
}
}
Demo: o que interessa
isAvailable() primeiro: sem NFC, ou com NFC desligado nas definições, a sessão nunca abre. Mostra uma mensagem, não um crash.
No Android, onDiscovered dispara em silêncio. No iOS aparece a folha do sistema e a sessão fecha sozinha ao fim de 60 segundos.
O payload de um Text Record começa com um byte de estado e o código da língua (pt, en): daí o skip(3).
Configuração: <uses-permission android:name="android.permission.NFC"/> no AndroidManifest.xml; no iOS, NFCReaderUsageDescription no Info.plist e a capability "Near Field Communication Tag Reading" no Xcode.
O HapticFeedback.heavyImpact() é o feedback: o utilizador sente que a leitura aconteceu antes de ler o ecrã.
Exercício 1 Se o vosso projeto tem um gesto que merece feedback (adicionar favorito, terminar o pull-to-refresh, receber um aviso vermelho), abre um PR de cinco linhas com HapticFeedback.selectionClick() ou mediumImpact() nesse ponto. Testa no telemóvel, não no emulador: o emulador não vibra.
Checklist para a review de quarta
App a correr no telemóvel de cada membro do grupo, a partir do main de terça à noite.
Demo de 5 minutos ensaiada: quem fala, que ecrãs, que localidade, que dados aparecem.
Modo avião ligado durante a demo: a app continua a mostrar dados e diz que são antigos.
Favoritos sincronizados entre dois telemóveis do grupo, ao vivo.
Lista do que ficou por fazer, sem desculpas. É matéria da retro, não é penalização.
Issues da sprint 2 fechadas ou movidas para a sprint 3 antes da aula.
Até segunda
Para fazer
A sprint 2 fecha na próxima quarta, 18/11: demo de 5 minutos preparada, com a checklist do slide anterior cumprida.
PRs abertos revistos por um colega do grupo até terça à noite; nada entra em main sem revisão.
Quem fez o exercício dos haptics: pede a revisão do PR e faz merge ainda hoje.
Para ler
Sensors overview no Android: as categorias de sensores, as taxas de amostragem e o custo em bateria.