Aula 05 · Semana 4 · Segunda, 28 de setembro de 2026 · 16:00 às 18:00
Teoria: o que o comando flutter create gera, a função de cada pasta, o pubspec.yaml e as diferenças entre debug e release. Prática: clonagem do primeiro repositório da UC e construção do primeiro ecrã da app Tempo Açores no telemóvel.
pubspec.yaml: dependências, versões com ^, assets, e o papel do pubspec.lock.main → runApp → MaterialApp → Scaffold → widgets.Text, Icon, Image, Container e Padding, e adicionar um package do pub.dev.flutter create gera| Pasta ou ficheiro | Para que serve |
|---|---|
lib/ | O teu código Dart. Tudo o que escreves vive aqui, a começar por main.dart. |
test/ | Testes automatizados. flutter test corre tudo o que aqui estiver (testes numa aula mais à frente). |
android/ e ios/ | Os projetos nativos que embrulham a app: Gradle e Kotlin de um lado, Xcode e Swift do outro. Só lá vais para permissões, ícones, assinatura. |
web/, macos/, linux/, windows/ | O mesmo para as outras plataformas. Podes apagar as que não usas. |
pubspec.yaml | O bilhete de identidade do projeto: nome, versão, dependências, assets, fontes. |
Para os testes: o código da app está em lib/; android/ e ios/ são projetos nativos gerados que o Flutter embrulha.
flutter create já gera um .gitignore correto. Não o apagues.build/ (binários, centenas de MB), .dart_tool/ (cache do compilador), .idea/ e *.iml (IDE), android/local.properties (caminhos do teu portátil).lib/, test/, pubspec.yaml, pubspec.lock, analysis_options.yaml, as pastas nativas, assets/.pubspec.lock commita-se em apps (todos compilam com as mesmas versões) e não se commita em packages publicados.key.properties da assinatura) nunca entram no git. Voltamos a isto numa aula mais à frente.git status mostrar centenas de ficheiros em build/, o .gitignore não está a funcionar. Corrige antes do primeiro commit.pubspec.yamlname: tempo_acores
description: Previsão meteorológica para os Açores.
publish_to: 'none'
version: 1.0.0+1 # versionName + versionCode (class 31)
environment:
sdk: ^3.11.0
dependencies: # ships inside the app
flutter:
sdk: flutter
google_fonts: ^8.2.1
intl: ^0.20.3
dev_dependencies: # development and tests only
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
assets:
- assets/img/
pubspec.yaml: o que interessadependencies entram no binário final; dev_dependencies (lints, testes, geradores de código) não.^6.2.1 significa "qualquer 6.x.y a partir de 6.2.1, mas não 7". É o semver: a versão major muda quando a API parte compatibilidade.flutter pub get resolve e descarrega; flutter pub add nome acrescenta ao pubspec e faz o get; flutter pub upgrade sobe para as versões mais recentes permitidas; flutter pub outdated mostra o que está atrasado.pubspec.lock fixa a versão exata que foi resolvida. Com ele, tu e os colegas compilam exatamente o mesmo código.flutter: declara assets e fontes. Uma imagem que não esteja aqui não existe para a app.Para os testes: ^1.2.3 aceita até <2.0.0; o pubspec.lock fixa versões exatas.
| Modo | Como compila | Para quê |
|---|---|---|
| Debug | JIT na Dart VM. Asserts ligados, hot reload, DevTools, banner "DEBUG" no canto. | Desenvolver. Lento e grande; nunca serve para medir desempenho. |
| Profile | AOT, como release, mas mantém a telemetria para o DevTools. | Medir desempenho num telemóvel real (mais à frente). |
| Release | AOT otimizado, sem asserts, sem DevTools, tree shaking. | O que vai para a loja e para os testers. |
Uma app que parece lenta em debug pode ser perfeitamente rápida em release. O contrário nunca acontece.
Para os testes: debug usa JIT e permite hot reload; release usa AOT e é o único modo que se distribui.
| Comando | O que faz |
|---|---|
flutter run | Compila em debug e instala no dispositivo escolhido. No VS Code é o F5, com o dispositivo escolhido em Flutter: Select Device; guardar um ficheiro faz hot reload e o botão ↻ faz hot restart. No terminal: r hot reload, R hot restart, q sair. -d escolhe o dispositivo, --release o modo. |
flutter build apk / appbundle / ipa | Gera o binário de release. O .aab é o que vai para o Google Play. |
flutter analyze | Corre o analisador estático e os lints sem compilar. Zero avisos é a regra do projeto. |
flutter test | Corre os testes em test/. |
flutter doctor / flutter devices | Estado do ambiente / dispositivos ligados e emuladores disponíveis. |
O VS Code e o Android Studio chamam exatamente estes comandos por baixo dos botões.
flutter run aparece um link no terminal; no VS Code, comando "Flutter: Open DevTools".debugPrint.Para os testes: o inspector é para layout, o separador Performance é para jank, e nenhum dos dois funciona em release.
main() é a porta de entrada. Chama runApp(widget), que liga o widget à raiz do ecrã e arranca o motor.MaterialApp é a raiz de quase todas as apps: define tema, rotas, localização e título. Há um por app.Scaffold é a estrutura de um ecrã: appBar em cima, body no meio, floatingActionButton, drawer, bottomNavigationBar. Há um por ecrã.body, os teus widgets: texto, ícones, imagens, listas, cartões.Para os testes: um MaterialApp por app, um Scaffold por ecrã, e runApp só se chama uma vez.
git clone https://github.com/Tiago-Rocha/pdm-aula-05.git
cd pdm-aula-05/starter
flutter pub get
Todos os repositórios das aulas têm a mesma forma: starter/ é o projeto por onde começas, com TODOs numerados no código e testes a falhar em test/; solucao/ é o estado final, para comparares no fim, não antes.
starter/ no VS Code. Ctrl+Shift+P → Flutter: Select Device → o teu Android. Depois F5: corre a configuração "Tempo Açores (debug)" que já vem no projeto. A app tem de aparecer no teu telemóvel com um texto no centro.lib/main.dart no VS Code e procura TODO 1.flutter test uma vez: vê os testes vermelhos. O objetivo da aula é pô-los verdes.main.dart em conjuntoimport 'package:flutter/material.dart';
void main() {
runApp(const WeatherApp());
}
class WeatherApp extends StatelessWidget {
const WeatherApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tempo Açores',
theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true),
home: Scaffold(
appBar: AppBar(title: const Text('Tempo Açores')),
body: const Center(
child: Text('TODO 1: substitui por um cartão'),
),
),
);
}
}
main.dart: o que interessaimport 'package:flutter/material.dart' traz o Material Design inteiro. Quase todos os ficheiros de UI começam assim.WeatherApp é um StatelessWidget: não tem estado próprio, só descreve. O build devolve a árvore.colorSchemeSeed gera uma paleta Material 3 inteira a partir de uma cor. Muda para Colors.deepOrange e faz hot reload.const à frente de um widget diz ao Flutter que aquela subárvore nunca muda: pode reutilizá-la em vez de reconstruir. Usa sempre que o compilador deixar.Center centra o filho; Text desenha texto. São os dois widgets mais simples que existem.Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Ponta Delgada',
style: Theme.of(context).textTheme.headlineSmall, // theme size, not fixed
),
const SizedBox(height: 8), // blank space
const Icon(Icons.wb_sunny, size: 48, color: Colors.orange),
Image.asset('assets/img/sao_miguel.jpg', height: 120, fit: BoxFit.cover),
Padding(
padding: const EdgeInsets.all(12),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.teal.shade50,
border: Border.all(color: Colors.teal),
borderRadius: BorderRadius.circular(12),
),
child: const Text('21 °C'),
),
),
],
)
Text com style do textTheme respeita o tema e o tamanho de letra do utilizador. Evita fontSize: 24 fixo.Icon usa os ícones Material incluídos (Icons.*). Não precisas de descarregar nada.Image.asset lê um ficheiro declarado em pubspec.yaml na secção assets:. Se te esqueceres, erro em runtime, não em compilação.Container junta cor, padding, borda, raio e tamanho num só widget. Padding e SizedBox são versões leves para quando só precisas de espaço.Column empilha filhos na vertical; amanhã vemos layout a fundo.TODO 1: substitui o texto do body por esta Column. Depois troca o ícone à mão, por exemplo para Icons.cloud ou Icons.umbrella, e a cor da borda. Confirma no telemóvel a cada hot reload. O ícone passa a depender dos dados amanhã.class LocationCard extends StatelessWidget {
const LocationCard({super.key, required this.name, required this.temperature});
final String name;
final double temperature;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Icon(Icons.wb_sunny, size: 40, color: Colors.orange),
const SizedBox(width: 16),
Text(name, style: textTheme.titleLarge),
const Spacer(),
Text('${temperature.round()} °C', style: textTheme.headlineMedium),
],
),
),
);
}
}
final, construtor const com parâmetros nomeados e um build. É exatamente o modelo de dados que escreveste no DartPad com um método a mais.const LocationCard(name: 'Horta', temperature: 19.4). O const só é possível porque o construtor é const e os argumentos são literais.Card dá elevação e cantos redondos do tema. Row põe os filhos lado a lado; Spacer empurra o resto para a direita.main.dart importa com import 'location_card.dart';.TODO 2: cria lib/location_card.dart, usa-o no body com três localidades numa Column, e acrescenta um parâmetro opcional IconData icon com valor por defeito. flutter test deve ficar verde no teste do cartão.Colors.teal para Colors.indigo e guarda o ficheiro: o VS Code faz hot reload ao guardar. A cor muda, o resto fica igual. Isto é hot reload: código novo, estado antigo.build foi chamado outra vez com o código novo.main() para runApp(const OutraApp()) (cria uma OutraApp que só tem um Text). Hot reload não chega: o main só corre no arranque. Hot restart: o botão ↻ da barra de depuração.assets/img/ e ao pubspec.yaml. Nem guardar nem o botão ↻ chegam: pára a app (■) e F5 outra vez.Para os testes: hot reload mantém o estado e volta a chamar build; hot restart reinicia a app; mudar o pubspec.yaml exige um novo flutter run.
flutter pub add google_fonts
import 'package:google_fonts/google_fonts.dart';
// inside MaterialApp
theme: ThemeData(
colorSchemeSeed: Colors.teal,
useMaterial3: true,
textTheme: GoogleFonts.interTextTheme(), // the whole app now uses Inter
),
pub add escreve no pubspec.yaml, faz o pub get e atualiza o pubspec.lock. Depois de uma dependência nova, pára a app (■) e F5 outra vez.intl com DateFormat('EEEE, d MMMM').format(DateTime.now()). Para datas em português precisa de initializeDateFormatting('pt_PT'); vemos numa aula mais à frente.TODO 3: adiciona google_fonts, aplica uma fonte à app e usa intl para mostrar a data de hoje por baixo do título. Faz commit com a mensagem feat: location card and fonts.TODOs do starter/ até o flutter test ficar verde. Se ficares preso, escreve no fórum do Moodle com a mensagem de erro.solucao/, compara com o que fizeste e anota as diferenças que não percebes. Trá-las para a aula de amanhã..gitignore está a excluir build/ e .dart_tool/ antes de fazer push.