PDM · DAW 2

Aula 05 · Semana 4 · Segunda, 28 de setembro de 2026 · 16:00 às 18:00

Anatomia de um projeto Flutter e primeira app

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.

Repositório: pdm-aula-05

No fim desta aula consegues

O que flutter create gera

Pasta ou ficheiroPara 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.yamlO 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.

O que se commita e o que não

Sinal de alarme Se o git status mostrar centenas de ficheiros em build/, o .gitignore não está a funcionar. Corrige antes do primeiro commit.

pubspec.yaml

name: 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 interessa

Para os testes: ^1.2.3 aceita até <2.0.0; o pubspec.lock fixa versões exatas.

Modos de compilação

ModoComo compilaPara quê
DebugJIT na Dart VM. Asserts ligados, hot reload, DevTools, banner "DEBUG" no canto.Desenvolver. Lento e grande; nunca serve para medir desempenho.
ProfileAOT, como release, mas mantém a telemetria para o DevTools.Medir desempenho num telemóvel real (mais à frente).
ReleaseAOT 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.

A linha de comandos

ComandoO que faz
flutter runCompila 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 / ipaGera o binário de release. O .aab é o que vai para o Google Play.
flutter analyzeCorre o analisador estático e os lints sem compilar. Zero avisos é a regra do projeto.
flutter testCorre os testes em test/.
flutter doctor / flutter devicesEstado do ambiente / dispositivos ligados e emuladores disponíveis.

O VS Code e o Android Studio chamam exatamente estes comandos por baixo dos botões.

DevTools

Para os testes: o inspector é para layout, o separador Performance é para jank, e nenhum dos dois funciona em release.

Anatomia de uma app

Para os testes: um MaterialApp por app, um Scaffold por ecrã, e runApp só se chama uma vez.

O primeiro repositório da UC

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.

  1. Clona. Abre a pasta 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.
  2. Abre lib/main.dart no VS Code e procura TODO 1.
  3. Corre flutter test uma vez: vê os testes vermelhos. O objetivo da aula é pô-los verdes.

1. Ler o main.dart em conjunto

import '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'),
        ),
      ),
    );
  }
}

1. main.dart: o que interessa

Atalho No VS Code, Ctrl+. (ou Cmd+.) em cima de um widget dá "Wrap with Padding", "Wrap with Center", "Remove this widget".

2. Widgets básicos

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'),
      ),
    ),
  ],
)

2. Widgets básicos: o que interessa

Exercício 1 Resolve o 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ã.

3. O cartão da localidade

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),
          ],
        ),
      ),
    );
  }
}

3. O cartão: o que interessa

Exercício 2 Resolve o 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.

4. Hot reload e hot restart na prática

  1. Com a app a correr, muda 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.
  2. Muda a temperatura de um cartão e faz hot reload. Muda. O build foi chamado outra vez com o código novo.
  3. Muda o 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.
  4. Acrescenta uma imagem nova a 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.

5. Adicionar um package

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
),
Exercício 3 Resolve o 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.

Até amanhã

Para fazer

  • Completa todos os TODOs do starter/ até o flutter test ficar verde. Se ficares preso, escreve no fórum do Moodle com a mensagem de erro.
  • Só depois abre solucao/, compara com o que fizeste e anota as diferenças que não percebes. Trá-las para a aula de amanhã.
  • Confirma que o teu .gitignore está a excluir build/ e .dart_tool/ antes de fazer push.

Para ler