PDM · DAW 2

Aula 09 · Semana 5 · Quarta, 7 de outubro de 2026 · 16:00 às 18:00

Navegação

Teoria: os ecrãs de uma app organizados numa pilha, o efeito do "voltar" no estado, e as rotas declaradas como URLs. Prática, na app das ilhas: abrir uma ilha e voltar, rotas com go_router e uma barra com três separadores.

Repositório: pdm-aula-09

No fim desta aula consegues

Hoje, na app (1 de 2): abrir uma ilha e voltar

1push
A grelha das ilhas, com um toque no Pico.
Tocas no Pico: o ecrã do Pico entra por cima da grelha.
2pop
O ecrã do Pico, com foto, os cinco dias e um toque na seta de voltar.
A seta ← fecha o ecrã do Pico.
3a grelha como estava
De volta à grelha, no mesmo sítio do scroll.
A grelha continua onde estava: o mesmo scroll, a mesma pesquisa.

É o que fazes nos passos 1 e 2: primeiro com Navigator.push, depois com o go_router e o caminho /island/pico. Na app, os dois passos ficam iguais.

Hoje, na app (2 de 2): três separadores

O Pico aberto no separador Previsão, com a barra de navegação em baixo. 3

Previsão: o Pico abre por cima da grelha e a barra continua lá.

O separador Avisos, com o formulário de avisos. 3

Avisos: o formulário de avisos, agora num separador. Voltas a Previsão e o Pico ainda está aberto.

O separador Definições, por agora só com a versão da app. 3

Definições: por agora só a versão; vai ganhar opções nas próximas aulas.

A barra de baixo é o passo 3 (NavigationBar com StatefulShellRoute): cada separador guarda a sua pilha.

A pilha de navegação

Uma pilha: push põe em cima, pop tira Grelha das ilhas vivo, scroll mantido Ilha vivo por baixo, State mantido Mapa topo: o que o utilizador vê push pop pop destrói o ecrã de cima: dispose() corre Três separadores, três pilhas cada separador guarda a sua pilha; mudar de separador não faz pop Grelha Ilha Mapa Lista Aviso Definições Previsão Avisos Definições
Uma pilha por fluxo: o ecrã de baixo fica vivo até fazeres pop; cada separador da barra tem a sua própria pilha.

Para os testes: push mantém o ecrã anterior vivo; pop destrói o ecrã atual.

Voltar: botão Android, gesto iOS, e o estado

◁ Android botão ou gesto do sistema deslizar iOS da margem esquerda ← Pico Os dois a seta da AppBar os três fazem pop

Para os testes: o "voltar" é do sistema, não da app; a app pode intercetá-lo com PopScope, mas não pode ignorá-lo.

Padrões de navegação

✕GuardarApagar?Não · SimLista → detalhepush do detalhe;pop volta à listaSeparadoresuma pilhapor separadorModalecrã inteiroque sobe de baixoBottom sheetpainel por cima,arrasta para fecharDiálogopergunta curta,duas ações
Hoje fazemos os dois primeiros: a grelha → o Pico, e a barra com três separadores.

Rotas imperativas vs declarativas

Imperativo: Navigator.push Navigator.push(context, MaterialPageRoute(...)) dizes o que fazer, ecrã a ecrã; a pilha só existe em memória, sem nome Declarativo: go_router, um mapa de caminhos /Grelha das ilhas/island/picoPico/alertsAvisos/settingsDefinições navegar é mudar o caminho, como num site
dependencies:                   # vai para dentro da app
  flutter:
    sdk: flutter
  go_router: ^18.0.2
  google_fonts: ^8.2.1

Para os testes: rotas declarativas descrevem os ecrãs como URLs e derivam a pilha do URL; são a base de deep links e da web.

Deep links em uma frase

Um deep link é um URL que abre a app diretamente num ecrã específico, em vez de no ecrã inicial.

1Um link
num SMS ou notificaçãotempoacores.pt/island/pico
O utilizador toca no link.
2O go_router
trata-o comocontext.go('/island/pico')
O mesmo caminho das nossas rotas.
3O ecrã certo
A app abre diretamente no ecrã do Pico.
A app abre no Pico, com a grelha por baixo.
Configuração e testes na aula 22. Hoje só desenhamos os caminhos.

Começar: o repositório de hoje

git clone https://github.com/Tiago-Rocha/pdm-aula-09.git
cd pdm-aula-09
flutter pub get
  1. Abre a pasta pdm-aula-09 no VS Code. É a Tempo Açores que já conheces, mais três ecrãs ainda por ligar: lib/island_screen.dart (uma ilha), lib/alerts_screen.dart (o formulário de avisos, que saiu do ecrã principal) e lib/settings_screen.dart (definições). Trabalhas nos comentários TODO 1 a TODO 3.
  2. Ctrl+Shift+P → Flutter: Select Device → o teu Android ou o Chrome. Depois F5.
  3. Confirma o que vês: o ecrã de previsão com a pesquisa e a grelha das ilhas. Tocar numa ilha ainda não faz nada.

O go_router já está no pubspec.yaml: o flutter pub get trata dele. Hoje o Chrome tem uma vantagem: a partir do passo 2 vês o caminho de cada ecrã na barra de endereço.

1. Navigator: push e pop

appBar: AppBar(title: Text(island.name)),
Pergunta à turma Escreves "pi" na pesquisa, abres o Pico e voltas com a seta. A pesquisa ainda lá está?

Passo 1: abrir uma ilha

return Card(
  clipBehavior: Clip.antiAlias,
  child: InkWell(
    onTap: () => Navigator.push(
      context,
      MaterialPageRoute(builder: (_) => IslandScreen(id: island.id)),
    ),
    child: Column(
      // a Column de antes, como está
    ),
  ),
);
Faz Em island_card.dart, embrulha a Column num InkWell com este onTap e importa o island_screen.dart.

Na app: tocar no Pico abre o ecrã do Pico, com a foto e os cinco dias; a seta ← volta à grelha, no mesmo sítio.

2. go_router: cada ecrã com um caminho

import 'package:go_router/go_router.dart';

import 'forecast_screen.dart';
import 'island_screen.dart';

/// Every screen of the app, with its path, like the pages of a site.
final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const ForecastScreen(),
      routes: [
        GoRoute(
          path: 'island/:id', // /island/pico
          builder: (context, state) =>
              IslandScreen(id: state.pathParameters['id']!),
        ),
      ],
    ),
  ],
);
Pergunta à turma Porque é que o caminho leva o id da ilha e não a Island inteira?

Passo 2: trocar o Navigator pelo go_router

git stash -u
git checkout passo-1
flutter pub get
return MaterialApp.router(
  title: 'Tempo Açores',
  theme: ThemeData(
    // o tema, como está
  ),
  routerConfig: router,
);
onTap: () => context.push('/island/${island.id}'),
Faz Escreve o router.dart do slide anterior. No main.dart, MaterialApp passa a MaterialApp.router, o home: sai e entra o routerConfig. No IslandCard, o onTap com o Navigator.push dá lugar a uma linha com context.push. Acerta os import.

Na app: tudo igual ao passo 1, e é esse o objetivo. No Chrome, ao abrir o Pico a barra de endereço mostra /island/pico; escrever /island/flores abre as Flores com a grelha por baixo.

3. Barra de navegação com pilhas independentes

StatefulShellRoute.indexedStack(
  builder: (context, state, shell) => Scaffold(
    body: shell, // the current tab, with its own stack
    bottomNavigationBar: NavigationBar(
      selectedIndex: shell.currentIndex,
      onDestinationSelected: (i) =>
          shell.goBranch(i, initialLocation: i == shell.currentIndex),
      destinations: const [
        NavigationDestination(
          icon: Icon(Icons.wb_sunny_outlined),
          label: 'Previsão',
        ),
        // e Avisos (Icons.warning_amber), Definições (Icons.settings_outlined)
      ],
    ),
  ),
  branches: [
    // um StatefulShellBranch por separador
  ],
),
Pergunta à turma Cada separador tem um Scaffold com a sua AppBar, e o shell tem outro Scaffold com a barra. Dois Scaffold um dentro do outro: é um erro?

Passo 3: três separadores

git stash -u
git checkout passo-2
flutter pub get
branches: [
  StatefulShellBranch(
    routes: [
      GoRoute(
        path: '/',
        // a rota / do passo 2, com a filha island/:id
      ),
    ],
  ),
  StatefulShellBranch(
    routes: [
      GoRoute(
        path: '/alerts',
        builder: (context, state) => const AlertsScreen(),
      ),
    ],
  ),
  // e o terceiro: '/settings' com o SettingsScreen
],
Faz No router.dart, a lista routes do GoRouter passa a ter um só elemento: o StatefulShellRoute.indexedStack do slide anterior. A rota / do passo 2 entra inteira no primeiro StatefulShellBranch; faz os outros dois. Importa o material.dart, o alerts_screen.dart e o settings_screen.dart.

Na app: uma barra em baixo com Previsão, Avisos e Definições. Abres o Pico, vais a Avisos, voltas a Previsão: o Pico continua aberto. Tocas outra vez em Previsão e voltas à grelha.

Onde ficámos

  1. IslandCard abre o IslandScreen com Navigator.push; a seta da AppBar faz pop (passo 1).
  2. router.dart com GoRouter: / e /island/:id, MaterialApp.router e context.push pelo caminho (passo 2).
  3. StatefulShellRoute.indexedStack com a NavigationBar: três separadores, cada um com a sua pilha (passo 3).

O branch passo-3 é o estado final: compara o teu código com ele.

Até segunda

Próxima aula

  • Segunda, 12 de outubro, das 16:00 às 18:00: acessibilidade, internacionalização e o mini-projeto individual.
  • O mini-projeto parte do que fizemos até hoje: grelha, ecrã de detalhe e navegação.
  • O estado final de hoje fica no branch passo-3: git stash -u e depois git checkout passo-3.

Para quem quiser ler mais