Aula 10 · Semana 6 · Segunda, 12 de outubro de 2026 · 16:00 às 18:00
Navegação a fundo e mini-projeto
Primeira parte: revemos a navegação da aula 9 com calma. As rotas como uma árvore, push e go, e o StatefulShellRoute peça a peça. Segunda parte: o primeiro mini-projeto avaliado, individual. Acrescentas à Tempo Açores um separador novo, Trilhos, com lista e detalhe.
Repositório: pdm-aula-10
No fim desta aula consegues
Ler um router.dart e desenhar a árvore de rotas que ele declara.
Explicar o que fazem GoRoute, StatefulShellRoute e StatefulShellBranch, e porque cada separador guarda a sua pilha.
Escolher entre context.push e context.go.
Acrescentar sozinho um separador novo a uma app, com lista, detalhe e rota com parâmetro.
As rotas são uma árvore
A tracejado, o que vais acrescentar hoje no mini-projeto. Uma rota filha não começa por /: o caminho dela junta-se ao da mãe.
Lê-se de cima para baixo: o GoRouter tem uma moldura, a moldura tem um branch por separador, cada branch tem as suas rotas.
/island/pico = rota / + filha island/:id. Por isso o Pico abre dentro do separador Previsão, com a barra visível.
Três peças, uma pergunta cada
Peça
Responde a
Na Tempo Açores
GoRoute
Que ecrã aparece neste caminho?
/alerts abre o AlertsScreen
StatefulShellRoute
O que fica à volta de todos os separadores?
Um Scaffold só com a NavigationBar
StatefulShellBranch
Que rotas pertencem a este separador?
Previsão: / e island/:id
O shell é só o miolo; a barra é desenhada à volta dele.
Pergunta à turma Abres o Pico, vais a Avisos e voltas a Previsão. Porque é que o Pico ainda lá está, e não a grelha?
O código da moldura
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',
),
NavigationDestination(
icon: Icon(Icons.warning_amber),
label: 'Avisos',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
label: 'Definições',
),
],
),
),
branches: [
// um StatefulShellBranch por separador, pela mesma ordem
],
),
shell.currentIndex: que separador está ativo. A barra pinta esse.
shell.goBranch(i): muda para o separador i, sem destruir os outros.
O destino i da barra abre o branch i. A ordem tem de ser a mesma nas duas listas.
Pergunta à turma Acrescentas uma NavigationDestination e esqueces-te do branch. O que acontece quando tocas no separador novo?
push ou go?
context.push(...) Põe por cima da pilha onde estás. O separador não muda; a seta de voltar regressa à lista.
context.go(...) Vai para a morada, como no URL. A barra salta para Trilhos; Previsão fica como estava, com o Faial aberto.
push pensa em pilhas; go pensa em moradas. Com go, o go_router monta a pilha do caminho: /trails por baixo, /trails/caldeira-faial por cima.
Usa quando
Exemplo
context.push
Abres um detalhe por cima do ecrã onde estás
Da grelha para uma ilha; da lista para um trilho
context.go
Vais para outro sítio da app, que tem a sua pilha
Da ilha para um trilho no separador Trilhos; um link de uma notificação
Receita: um separador novo
O ecrã. Um widget com Scaffold e AppBar, como os outros separadores.
O destino na barra. Uma NavigationDestination com ícone e etiqueta, na posição em que o queres ver.
O branch. Um StatefulShellBranchna mesma posição, com a GoRoute do caminho do separador.
O detalhe, se houver. Uma rota filha dentro dessa GoRoute, com o parâmetro: ':id', sem / no início.
O ecrã de detalhe recebe o id, procura o objeto nos dados e trata o caso de não o encontrar.
É isto que vais fazer no mini-projeto, com Trilhos. O separador Previsão, com / e island/:id, é o modelo completo: está no mesmo ficheiro.
Mini-projeto: Trilhos
Individual e avaliado: é a primeira entrega da UC.
Começas no repositório pdm-aula-10, que usas como template para criares o teu. Lá dentro está a Tempo Açores com os três separadores e os trilhos já nos dados. Acrescentas um quarto separador, Trilhos.
Tocar num trilho abre o detalhe. Cada ilha passa a mostrar os seus trilhos.
Hoje, em aula: crias o repositório, convidas o docente e começas. Entregas até quarta, 14 de outubro, às 16:00.
Já vem feito
A Tempo Açores com os três separadores: Previsão, Avisos e Definições.
lib/data.dart com 13 trilhos, a classe Trail e o enum Difficulty.
lib/trails_screen.dart e lib/trail_screen.dart, vazios.
Fazes tu
Tarefa 1: o separador na barra e as rotas.
Tarefa 2: a lista de trilhos.
Tarefa 3: o detalhe de um trilho.
Tarefa 4: os trilhos de cada ilha.
Criar o teu repositório
Em github.com/Tiago-Rocha/pdm-aula-10, com a sessão iniciada: botão verde Use this template e depois Create a new repository. Não é o Fork.
Nome pdm-miniprojeto, visibilidade Private, e Create repository.
No teu repositório novo, Settings, depois Collaborators, depois Add people: escreve Tiago-Rocha.
Clonar e primeiro commit
No teu repositório, botão verde Code, separador HTTPS: copia o URL. Tem de ter o teu utilizador e pdm-miniprojeto, não pdm-aula-10.
cd C:\dev
git clone https://github.com/<utilizador>/pdm-miniprojeto.git
cd pdm-miniprojeto
flutter pub get
Na linha do git clone, cola o URL que copiaste. Abre a pasta pdm-miniprojeto no VS Code (File, Open Folder), escolhe o Android ou o Chrome, e F5.
Primeiro commit, todos juntos: no README.md, na primeira linha, **Student:** Maria Silva Medeiros · 2024123 com o teu nome e número. Commit com a mensagem Nome do aluno no README e Sync Changes. No GitHub, o nome aparece no topo.
Na app: a Tempo Açores com três separadores: Previsão, Avisos e Definições. O separador Trilhos ainda não existe; é a Tarefa 1.
Guardar e entregar
Em cada tarefa acabada
Vista Source Control (Ctrl+Shift+G).
Mensagem que diga o que mudou: Separador Trilhos com as rotas.
Commit e depois Sync Changes.
Atualiza a página do teu repositório no GitHub: o commit está lá?
Para entregar
Último commit, com Sync, antes de quarta às 16:00.
No README: o que fizeste, o que ficou por fazer, e duas capturas (lista e detalhe).
Mais nada. O docente vê o teu repositório pelo convite de colaborador: a entrega é o repositório.
O que conta O que está no GitHub à hora do prazo. Um commit sem Sync só existe no teu portátil, e para a avaliação não existe.
Os dados: lib/data.dart
enum Difficulty {
easy('Fácil', Colors.green),
medium('Média', Colors.orange),
hard('Difícil', Colors.red);
// ...
}
class Trail {
final String id; // used in the route: /trails/lagoa-do-fogo
final String name;
final String islandId; // the id of an Island: 'sao-miguel', 'pico', ...
final double distanceKm;
final String duration; // '3 h 30'
final Difficulty difficulty;
final String description;
/// The island of this trail, with the name and the photo.
Island get island => islands.firstWhere((i) => i.id == islandId);
// e distance: '10,5 km'
}
const trails = [
// 13 trilhos, pelo menos um em cada ilha
];
trail.island.image dá a foto da ilha do trilho; trail.island.name, o nome.
trail.distance já vem formatado: '10,5 km'.
trail.difficulty.label e trail.difficulty.color: mostra sempre o texto, não só a cor.
Como deve ficar
21
Trilhos (Tarefas 1 e 2): um ListTile por trilho. À esquerda a foto da ilha num CircleAvatar; o nome; por baixo "Ilha · distância"; à direita a dificuldade, com bolinha de cor e texto.
3
Detalhe (Tarefa 3): o nome na AppBar, com a seta de voltar. A foto da ilha em cima; por baixo, a ilha, uma linha com a distância, a duração e a dificuldade, e a descrição.
4
Ilha (Tarefa 4): por baixo dos dias, o título "Trilhos" e as mesmas linhas da lista, só com os trilhos dessa ilha.
Não tem de ficar igual ao pixel, mas tem de ter estes elementos. Dica: a linha da lista e a linha da ilha são iguais. Faz um widget para a linha e usa-o nos dois sítios.
Tarefas 1 e 2: o separador e a lista
Tarefa 1 Em lib/router.dart: uma NavigationDestination Trilhos (Icons.hiking) e o seu StatefulShellBranch, na mesma posição. A GoRoute'/trails' abre o TrailsScreen; dentro dela, a rota filha ':id' abre o TrailScreen.
Na app: quatro separadores na barra. Trilhos abre um ecrã que diz "Ainda sem trilhos". No Chrome, o URL é /trails.
Tarefa 2 Em lib/trails_screen.dart: um ListView.builder com um ListTile por trilho. Foto da ilha (CircleAvatar com AssetImage), nome, ilha e distância, e a dificuldade. onTap: context.push('/trails/${...}').
Na app: os 13 trilhos, de São Miguel ao Corvo. Tocar num abre um ecrã com o id no título; a seta de voltar volta à lista.
Acabaste uma tarefa? Commit e Sync Changes, antes de passares à seguinte.
Tarefas 3 e 4: o detalhe e a ilha
Tarefa 3 Em lib/trail_screen.dart: procura o trilho com este id em trails, como o IslandScreen faz com as ilhas. Mostra a foto da ilha, o nome, a distância, a duração, a dificuldade e a descrição. Se o id não existir, "Trilho não encontrado".
Na app: a Montanha do Pico com foto, 7,6 km, 7 h, Difícil e a descrição. No Chrome, /trails/xyz mostra "Trilho não encontrado".
Tarefa 4 Em lib/island_screen.dart, por baixo dos dias: um título "Trilhos" e os trilhos dessa ilha (trails.where(...) com o islandId). Tocar num: context.go('/trails/${...}').
Na app: no Faial aparecem dois trilhos. Tocar na Caldeira muda para o separador Trilhos, com a Caldeira aberta. Voltas a Previsão: o Faial continua lá.
final island = islands.where((i) => i.id == id).firstOrNull;
if (island == null) {
// an id that does not exist, for example a mistyped link
return Scaffold(
appBar: AppBar(),
body: const Center(child: Text('Ilha não encontrada')),
);
}
Extras, para quem acabar
Filtro por dificuldade no topo da lista (SegmentedButton ou FilterChip, com setState).
Pesquisa de trilhos por nome, com mensagem quando não há resultados (como a pesquisa das ilhas).
Na ilha, o número de trilhos no título e uma mensagem quando não há nenhum.
Transição Hero da foto entre a lista e o detalhe.
Os extras não contam para a nota. Primeiro as quatro tarefas.
Entrega e avaliação
Hoje, antes de saíres Repositório criado a partir do template, docente convidado como colaborador, primeiro commit com o teu nome, e as tarefas começados.
Prazo: quarta-feira, 14 de outubro, às 16:00, no início da aula 12. Conta o último commit que estiver no GitHub a essa hora: faz sempre Sync.
A entrega é o repositório. Sem convite de colaborador e sem o teu nome no README, não há entrega.
Vale 2 valores (10 % da nota final):
Tarefa
Valor
1: o separador Trilhos, com as rotas
0,5
2: a lista de trilhos
0,5
3: o detalhe, com o caso "não encontrado"
0,5
4: os trilhos na ilha, com context.go
0,5
Se a app não compilar, a nota é 0. Podes usar IA, mas o docente pode pedir-te numa aula para explicares o teu código. Código que não sabes explicar não conta.
Até amanhã
Próxima aula
Terça, 13 de outubro, das 8:30 às 10:30: qualidade de código, lints, organização e git. Traz o mini-projeto como estiver: vamos trabalhar nele.
Entrega do mini-projeto: quarta, 14 de outubro, às 16:00, no GitHub.