O que você vai construir
Você não precisa saber criar uma página Flutter antes de começar. A página será construída neste módulo, uma parte de cada vez. Ao final, você terá um projeto chamado developer_profile com:
- uma aplicação configurada com
MaterialAppeThemeData; - uma página
DeveloperProfilePageem arquivo próprio; - nome, especialidade, localização e botão de contato;
- layout que continua utilizável em uma janela estreita;
- feedback visual ao clicar no botão;
- um teste automatizado e um README com instruções de execução.
Não cole todo o código final de uma vez. Execute o aplicativo em cada checkpoint. Assim, se algo falhar, você saberá em qual pequena mudança procurar o erro.
1. O modelo mental do Flutter
Flutter é um toolkit de interface multiplataforma. Você escreve uma descrição declarativa da interface em Dart, e o framework transforma essa descrição em estruturas persistentes de layout e renderização. Em plataformas nativas, o código de desenho é compilado e o engine do Flutter produz os pixels, atualmente usando Impeller nas plataformas compatíveis.
Para quem vem de .NET MAUI, a comparação serve como ponto de partida, não como tradução literal:
| .NET MAUI | Flutter |
|---|---|
| C# e runtime .NET | Dart e runtime Dart |
| XAML ou C# | Composição declarativa em Dart |
| Controles da plataforma ou handlers | Conjunto de widgets e engine do Flutter |
| Binding e propriedades observáveis | Reconstrução orientada a estado |
ContentPage | Composição comum com Scaffold |
Três estruturas ajudam a entender o que acontece:
- Widget: configuração imutável que você declara, como
TextouPadding. - Element: instância persistente que liga um widget à sua posição na árvore. O
BuildContextrepresenta essa posição. - RenderObject: participa de layout, pintura e hit testing quando aquele widget precisa renderizar algo.
No início, você trabalhará quase sempre com widgets. Conhecer as outras estruturas evita a ideia equivocada de que todo objeto é destruído e redesenhado integralmente a cada build().
DeveloperProfileApp
└── MaterialApp
└── DeveloperProfilePage
└── Scaffold
├── AppBar
└── SafeArea
└── SingleChildScrollView
└── Column
├── CircleAvatar
├── Text
└── FilledButton
Cada nível resolve uma responsabilidade. Flutter favorece composição: em vez de configurar dezenas de propriedades em um controle, você combina widgets pequenos.
2. Prepare e diagnostique o ambiente
Instale o Flutter pelo guia oficial da sua plataforma. Para o primeiro exercício, web com Chrome costuma ser o caminho mais curto. Android exige também Android Studio, SDK, licenças e um dispositivo ou emulador; Windows desktop exige a toolchain C++ do Visual Studio.
Abra PowerShell, Terminal ou o terminal integrado do editor e execute:
flutter --version
flutter doctor -v
flutter devices
flutter doctor -vflutter doctor não testa seu aplicativo. Ele diagnostica o ambiente de desenvolvimento: encontra o SDK Flutter e verifica toolchains conhecidas, como Android SDK, Xcode no macOS, navegadores, IDEs e dispositivos. A opção -v mostra caminhos e versões, o que ajuda a detectar dois SDKs instalados ou uma diferença entre terminal e IDE.
✓ indica sucesso, ! pede atenção e ✗ indica uma falha. Interprete o resultado conforme o alvo escolhido: a falta do Xcode não bloqueia web ou Android no Windows; a falta do Android toolchain bloqueia um build Android.
O comando não corrige tudo sozinho. Ele entrega evidências para você decidir o que instalar ou configurar.
Use esta tabela para decidir se pode continuar:
| Quero executar em | Evidência mínima |
|---|---|
| Chrome | Chrome aparece em flutter devices |
| Android físico | aparelho aparece em flutter devices e o Android toolchain não possui erro bloqueador |
| Emulador Android | emulador está iniciado e aparece em flutter devices |
| Windows | Windows aparece em flutter devices e a toolchain do Visual Studio está válida |
Se nenhum dispositivo aparecer
- Confirme que o alvo está aberto: inicie o Chrome, o emulador ou conecte o telefone.
- Execute novamente
flutter devices. - Para Android físico, habilite depuração USB e aceite a autorização mostrada no aparelho.
- Volte ao
flutter doctor -ve corrija somente o item relacionado ao seu alvo.
Checkpoint 1: não avance enquanto você não conseguir apontar um identificador retornado por flutter devices, como chrome, windows ou o ID de um telefone.
3. Crie e abra o projeto
Primeiro escolha uma pasta de trabalho. Não crie o projeto dentro de outro projeto Flutter. No terminal, navegue até essa pasta e execute:
flutter create developer_profile
cd developer_profile
flutter devices
flutter run -d chrome
Se você escolheu outro dispositivo, substitua chrome pelo ID exibido em flutter devices.
flutter create developer_profileO comando criou a pasta developer_profile, o projeto Dart, o pubspec.yaml, um teste inicial e as integrações das plataformas habilitadas no seu SDK. Ele também resolveu as dependências iniciais.
O nome usa snake_case porque também se torna o nome do package Dart. Em um produto real, opções como organização e plataformas devem ser definidas antes da geração; veja flutter create --help.
flutter run -d chromeO Flutter resolve dependências, compila o código em modo de desenvolvimento, inicia a aplicação no Chrome e mantém o terminal conectado ao Dart VM Service. A primeira execução costuma demorar mais.
Mantenha esse terminal aberto. Durante a sessão, r solicita Hot Reload, R faz Hot Restart e q encerra a aplicação.
Você deve ver o aplicativo-contador criado pelo template. Se vir essa tela, o ambiente, o projeto e o dispositivo já estão conectados.
Agora abra a pasta developer_profile, não apenas um arquivo:
- VS Code: File > Open Folder e escolha
developer_profile; ou executecode .se esse comando estiver configurado. - Android Studio: Open e escolha
developer_profile.
Checkpoint 2: o projeto padrão está aberto no editor e executando no dispositivo escolhido.
4. Reconheça o projeto antes de editar
Você encontrará uma estrutura semelhante a esta:
developer_profile/
├── android/ integração com Android
├── ios/ integração com iOS
├── web/ bootstrap da aplicação web
├── windows/ integração com Windows
├── lib/
│ └── main.dart entrada atual da aplicação
├── test/
│ └── widget_test.dart teste criado pelo template
├── pubspec.yaml metadados, SDK, dependências e assets
└── README.md instruções para outras pessoas
As pastas de plataforma não são o lugar da sua interface Flutter. Neste exercício, você editará somente:
lib/main.dart;lib/developer_profile_page.dart, que ainda será criado;test/widget_test.dart;README.md.
Em main.dart, três pontos formam a cadeia inicial:
main()é a entrada do processo Dart;runApp()entrega o widget raiz ao framework;build()descreve a interface correspondente à configuração e ao estado atuais.
O método build() pode ser chamado muitas vezes e deve ser rápido e sem efeitos colaterais: ele descreve widgets; não é o lugar para gravar arquivo, chamar API ou alterar banco de dados.
5. Checkpoint 3 — crie a estrutura da aplicação
No editor, abra lib/main.dart, apague o conteúdo criado pelo template e cole:
import 'package:flutter/material.dart';
import 'developer_profile_page.dart';
void main() {
runApp(const DeveloperProfileApp());
}
class DeveloperProfileApp extends StatelessWidget {
const DeveloperProfileApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Perfil do desenvolvedor',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const DeveloperProfilePage(),
);
}
}
O editor mostrará um erro em developer_profile_page.dart e em DeveloperProfilePage. Isso é esperado: o arquivo importado ainda não existe.
Crie agora o arquivo lib/developer_profile_page.dart. Confira o nome cuidadosamente: letras minúsculas, palavras separadas por _ e extensão .dart. Adicione a primeira versão da página:
import 'package:flutter/material.dart';
class DeveloperProfilePage extends StatelessWidget {
const DeveloperProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Curso Flutter & Dart'),
),
body: const Center(
child: Text('Minha primeira página'),
),
);
}
}
Salve os dois arquivos. Se o aplicativo ainda estiver executando, pressione r no terminal. Se você o encerrou, execute novamente:
flutter run -d chrome
Você deve ver uma barra superior com “Curso Flutter & Dart” e o texto “Minha primeira página” no centro.
O que cada widget resolveu
| Código | Responsabilidade |
|---|---|
MaterialApp | configura navegação, tema e serviços Material da aplicação |
ThemeData | centraliza decisões visuais compartilhadas |
DeveloperProfilePage | representa a página que estamos construindo |
Scaffold | fornece a estrutura visual básica de uma tela Material |
AppBar | ocupa a área superior do Scaffold |
Center | oferece restrições mais flexíveis e centraliza seu filho |
Text | descreve o texto visível |
Checkpoint 3: a página mínima compila e aparece. Se isso não acontecer, não adicione o cartão ainda.
6. Checkpoint 4 — monte o perfil de forma responsiva
Agora substitua somente o método build() de DeveloperProfilePage por esta versão:
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('Curso Flutter & Dart'),
),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 520),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CircleAvatar(
radius: 48,
backgroundColor: colors.primaryContainer,
foregroundColor: colors.onPrimaryContainer,
child: const Icon(Icons.person_outline, size: 48),
),
const SizedBox(height: 24),
Text(
'João Silva',
textAlign: TextAlign.center,
style: textTheme.headlineMedium,
),
const SizedBox(height: 8),
Text(
'Desenvolvedor .NET aprendendo Flutter',
textAlign: TextAlign.center,
style: textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
'São Paulo, Brasil',
textAlign: TextAlign.center,
style: textTheme.bodyLarge,
),
const SizedBox(height: 32),
FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.mail_outline),
label: const Text('Entrar em contato'),
),
],
),
),
),
),
),
);
}
Salve e use Hot Reload. A página deve exibir avatar, nome, especialidade, cidade e botão.
O layout inclui decisões que evitam o clássico overflow do iniciante:
SafeAreaevita áreas reservadas pelo sistema;SingleChildScrollViewpermite rolagem vertical quando a altura é pequena;Paddingimpede que o conteúdo encoste nas bordas;ConstrainedBox(maxWidth: 520)evita uma coluna excessivamente larga;Columnorganiza os filhos verticalmente;crossAxisAlignment: stretchpermite que o botão use a largura disponível.
A regra de layout que você deve guardar é: restrições descem, tamanhos sobem e o pai define a posição. Um filho não escolhe livremente qualquer tamanho; ele precisa respeitar os limites recebidos.
Por que algumas linhas não usam const?
Text('João Silva') recebe um estilo obtido em tempo de execução por Theme.of(context), portanto essa instância não pode ser const. Já SizedBox(height: 24) e Icon(Icons.person_outline) dependem apenas de valores constantes e podem ser criados como const.
Checkpoint 4: redimensione a janela até ficar estreita. Não deve aparecer a faixa amarela e preta de overflow. Em uma altura muito pequena, deve existir rolagem.
7. Checkpoint 5 — transforme o botão em uma ação
Um botão com onPressed: () {} está habilitado, mas não faz nada. Substitua esse callback por:
onPressed: () {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
const SnackBar(
content: Text('Obrigado! Em breve entraremos em contato.'),
),
);
},
O callback é a função executada depois do clique. Ele é explícito: o código não chama a ação durante o build(); entrega uma função ao botão para ser executada no futuro.
O context permite encontrar o ScaffoldMessenger disponível naquela posição da árvore. O operador em cascata .. executa duas operações no mesmo objeto: esconde uma mensagem anterior e exibe a nova.
Clique em “Entrar em contato”. A mensagem deve aparecer na parte inferior da página.
Se
onPressedfossenull, o Flutter mostraria o botão desabilitado.onPressed: () {}habilita o botão, mas ainda não implementa comportamento útil.
Checkpoint 5: cada clique produz feedback visível e não gera erro no terminal.
8. Código completo da página
Ao final dos checkpoints, lib/developer_profile_page.dart deve estar assim:
import 'package:flutter/material.dart';
class DeveloperProfilePage extends StatelessWidget {
const DeveloperProfilePage({super.key});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('Curso Flutter & Dart'),
),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 520),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CircleAvatar(
radius: 48,
backgroundColor: colors.primaryContainer,
foregroundColor: colors.onPrimaryContainer,
child: const Icon(Icons.person_outline, size: 48),
),
const SizedBox(height: 24),
Text(
'João Silva',
textAlign: TextAlign.center,
style: textTheme.headlineMedium,
),
const SizedBox(height: 8),
Text(
'Desenvolvedor .NET aprendendo Flutter',
textAlign: TextAlign.center,
style: textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
'São Paulo, Brasil',
textAlign: TextAlign.center,
style: textTheme.bodyLarge,
),
const SizedBox(height: 32),
FilledButton.icon(
onPressed: () {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
const SnackBar(
content: Text(
'Obrigado! Em breve entraremos em contato.',
),
),
);
},
icon: const Icon(Icons.mail_outline),
label: const Text('Entrar em contato'),
),
],
),
),
),
),
),
);
}
}
Compare seu arquivo com essa versão apenas depois de concluir os checkpoints. Se houver diferença apenas de espaços ou quebras de linha, o formatador resolverá.
9. Experimente Hot Reload, Hot Restart e reinício completo
Com a aplicação executando:
- Troque
João Silvapelo seu nome, salve e pressioner. O texto deve mudar sem perder a sessão. - Altere o
seedColoremmain.dartdeColors.indigoparaColors.teale use Hot Reload. - Se a alteração feita em uma inicialização não aparecer como esperado, pressione
Rpara Hot Restart.
| Ação | O que preserva | Quando usar |
|---|---|---|
Hot Reload (r) | tenta preservar o estado Dart existente | mudanças na implementação de widgets e métodos |
Hot Restart (R) | reinicia o código Dart e perde estado em memória | inicializadores, estado que precisa voltar ao começo |
| Reinício completo | reinicia também a aplicação nativa | plugins, permissões e integração de plataforma |
Hot Reload acelera o feedback, mas não prova que o aplicativo inicia corretamente desde o zero. Antes de concluir o módulo, encerre com q e execute o projeto novamente.
10. Formate e analise antes de testar
Abra um segundo terminal na raiz de developer_profile e execute:
dart format lib test
flutter analyze
flutter analyzeO analisador verifica o projeto com as regras definidas em analysis_options.yaml. Ele encontra referências inválidas, problemas de tipos e violações de lint sem precisar executar todos os caminhos da aplicação.
No issues found! é o resultado esperado. Leia a primeira mensagem de erro, corrija-a e execute o comando novamente; um único parêntese ausente pode produzir vários erros em cascata.
11. Escreva o primeiro teste guiado
O teste criado pelo template ainda procura o contador que removemos. Abra test/widget_test.dart, substitua todo o conteúdo e confirme que o nome do package no import é developer_profile:
import 'package:developer_profile/main.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('exibe o perfil e confirma o contato', (tester) async {
await tester.pumpWidget(const DeveloperProfileApp());
expect(find.text('João Silva'), findsOneWidget);
expect(find.text('Entrar em contato'), findsOneWidget);
await tester.tap(find.text('Entrar em contato'));
await tester.pump();
expect(
find.text('Obrigado! Em breve entraremos em contato.'),
findsOneWidget,
);
});
}
Execute:
flutter test
O teste monta a aplicação em um ambiente controlado, confirma dois textos, simula o clique e verifica o feedback. Ele não substitui o teste visual em janela estreita; cada validação responde a uma pergunta diferente.
Checkpoint 6: formatação, análise e teste terminam com sucesso.
12. Documente como executar
Abra README.md e registre, no mínimo:
# Developer Profile
Primeira interface do curso Flutter & Dart.
## Ambiente validado
- Flutter: cole aqui a saída de `flutter --version`
- Alvo utilizado: Chrome, Android ou Windows
## Executar
```bash
flutter pub get
flutter devices
flutter run -d chrome
```
## Validar
```bash
dart format --output=none --set-exit-if-changed lib test
flutter analyze
flutter test
```
Substitua chrome pelo alvo real usado por você. Um README não é decoração: ele permite que outra pessoa reproduza seu ambiente e sua validação.
Erros comuns e como raciocinar
| Sintoma | Causa provável | Próxima ação |
|---|---|---|
Target of URI doesn't exist | nome ou caminho de developer_profile_page.dart não corresponde ao import | confira arquivo, extensão e _ |
The name 'DeveloperProfilePage' isn't a class | classe ausente ou com nome diferente | compare import, nome da classe e maiúsculas |
vários erros Expected to find | parêntese, colchete ou vírgula ausente antes da linha indicada | examine primeiro o bloco imediatamente anterior |
| faixa amarela e preta | um RenderFlex excedeu as restrições disponíveis | confira SingleChildScrollView, textos e largura |
| botão cinza e inativo | onPressed está null | forneça um callback |
| alteração não aparece | Hot Reload preservou uma inicialização anterior | tente Hot Restart e entenda o motivo |
No devices found | alvo não está iniciado ou autorizado | volte a flutter devices e flutter doctor -v |
teste ainda procura 0 ou botão + | teste original do contador não foi substituído | atualize test/widget_test.dart |
Evite “resolver” copiando outra versão inteira do projeto. Leia a primeira mensagem, identifique o arquivo e reduza o problema ao último checkpoint que funcionava.
Prática autônoma — agora personalize
Somente depois de a versão guiada funcionar:
- Troque nome, especialidade e cidade pelos seus dados.
- Escolha outra cor-semente em
ThemeData; não espalhe cores fixas pela página. - Adicione uma segunda informação profissional com ícone.
- Atualize o teste para procurar seu nome.
- Execute novamente formatação, análise e testes.
Critérios de conclusão
- O projeto parte de um checkout limpo com
flutter pub geteflutter run -d <deviceId>. - A página possui
AppBar, dados profissionais e callback com feedback visível. - A janela estreita não produz overflow e permite rolagem quando necessário.
- O tema está em
ThemeData; a página consomeTheme.of(context). dart format,flutter analyzeeflutter testterminam com sucesso.- O README registra versão, alvo, execução e validação.
- Você consegue explicar cada widget que adicionou; não apenas reproduzir o código.
Verifique seu entendimento
- Qual é a diferença entre Widget, Element e RenderObject?
- Por que
build()não deve fazer chamadas de API ou alterar banco de dados? - O que
SingleChildScrollViewresolve — e o que ele não resolve? - Por que o estilo de
Textobtido deTheme.of(context)impede aquele widget de serconst? - Em que situação Hot Reload não é suficiente?
- O que o teste automatizado prova e o que ainda precisa de inspeção visual?
Conferir respostas esperadas
- Widget é configuração imutável; Element mantém a posição/identidade na árvore; RenderObject participa de layout e pintura.
build()pode ser chamado muitas vezes e deve apenas descrever a interface, sem efeitos colaterais.- Ele permite rolagem quando falta altura, mas não corrige toda restrição incorreta nem torna qualquer layout automaticamente responsivo.
- O estilo depende do contexto e do tema disponíveis em tempo de execução.
- Quando é necessário reinicializar estado Dart,
main()ou integração nativa; use Hot Restart ou reinício completo conforme o caso. - O teste prova presença, interação e feedback definidos; não prova legibilidade, ausência de overflow em todo tamanho nem qualidade visual.
Próximo passo: no Módulo 2, você vai entender a linguagem usada neste código — tipos, null safety, coleções, Futures, Streams e modelagem de domínio — antes de aumentar a complexidade da aplicação.