Cheatsheet Flutter
Framework para desenvolvimento mobile
Flutter
Sintaxe Básica (Dart)
Variáveis
int idade = 30; double preco = 9.99; String nome = "Ana"; bool ativo = true; var total = 100; // tipo inferido (int) dynamic x = "texto"; // aceita qualquer tipo
Usa var quando queres que o Dart infira o tipo. dynamic desativa a verificação de tipos e deve ser evitado em código production.
Interpolação de strings
String nome = "Ana";
int idade = 30;
String s = "Olá $nome";
String r = "Total: ${preco * 2}";
String m = "Nome: ${nome.toUpperCase()}";Usa $variavel para inserir valores simples e ${expressao} para expressões ou chamadas de método dentro da string.
Operadores null-aware
String? nome; nome ??= "Anónimo"; // atribui se null var x = nome ?? "N/D"; // fallback // Acesso seguro em cadeia int? len = nome?.length; var cidade = usuario?.endereco?.cidade;
O ??= atribui só se a variável for null. O ?. interrompe a cadeia e devolve null em vez de lançar exceção.
late (inicialização tardia)
class Servico {
late final ApiClient api;
Servico() {
api = ApiClient(); // inicializada depois
}
}
late String config = carregarConfig();O late permite declarar uma variável non-null que será inicializada mais tarde. O Dart só verifica a atribuição no primeiro acesso.
final e const
final nome = "Ana"; // atribuída uma vez (runtime) const pi = 3.14; // constante de compilação final lista = [1, 2]; lista.add(3); // OK: o conteúdo pode mudar // lista = [4]; // ERRO: não pode reatribuir
final permite uma única atribuição em runtime. const é imutável desde a compilação e deve ter valor conhecido estaticamente.
Métodos de String
String s = " Olá Mundo ";
s.trim(); // "Olá Mundo"
s.toLowerCase(); // minúsculas
s.toUpperCase(); // maiúsculas
s.length; // comprimento
s.contains("Mundo"); // true
s.split(" "); // lista de palavras
s.replaceAll("o", "0"); // substituiAs String são imutáveis: cada método devolve uma nova string. trim() remove espaços e split() devolve uma List<String>.
Cascade (..)
var lista = [] ..add(1) ..add(2) ..add(3) ..sort(); var paint = Paint() ..color = Colors.blue ..strokeWidth = 2.0;
O operador .. encadeia várias operações sobre o mesmo objeto sem repetir o nome. Muito útil para configurar objetos de forma compacta.
Type aliases (typedef)
typedef Callback = void Function(String);
typedef Par = (int, int);
void registar(Callback cb) {
cb("ok");
}
registar((msg) => print(msg));O typedef cria um nome alternativo para um tipo, útil para assinaturas de funções complexas. Em Dart 3 também define records nomeados.
Tipos de dados
int n = 10; // inteiro
double d = 3.14; // decimal
num x = 5; // int ou double
String s = "texto"; // texto
bool b = true; // booleano
List<int> l = [1, 2]; // lista
Map<String, int> m = {}; // mapa
Set<int> c = {1, 2}; // conjuntoO Dart é fortemente tipado. num é a superclasse de int e double. Coleções usam genéricos como List<int>.
Operadores aritméticos
int a = 10, b = 3; a + b; // 13 soma a - b; // 7 subtração a * b; // 30 multiplicação a / b; // 3.33 divisão (double) a ~/ b; // 3 divisão inteira a % b; // 1 resto (módulo) a++; // incremento
A divisão / devolve sempre double. Usa ~/ para obter o quociente inteiro e % para o resto da divisão.
Conversão de tipos
int n = int.parse("42");
double d = double.parse("3.14");
String s = n.toString();
String f = 3.14.toStringAsFixed(1); // "3.1"
int? erro = int.tryParse("abc"); // null (sem exceção)int.parse() e double.parse() convertem strings em números e lançam exceção se inválidas. tryParse() devolve null em vez de falhar.
Null safety
String? nome; // aceita null nome ??= "Anónimo"; // atribui se for null int len = nome!.length; // força non-null String s = nome ?? "padrão"; // valor alternativo String? nulo = null; nulo?.length; // retorna null, não erro
Por defeito os tipos não aceitam null. O ? torna o tipo nullable, ?? dá um fallback e ?. evita exceções em cadeias.
Comparação e lógicos
// Comparação 5 == 5; // true 5 != 3; // true 5 > 3; // true 5 <= 5; // true // Lógicos true && false; // false (E) true || false; // true (OU) !true; // false (negação)
Os operadores && (E) e || (OU) são de curto-circuito: o segundo operando só é avaliado se necessário. ! nega um bool.
Números (int e double)
double d = 9.876; d.round(); // 10 d.floor(); // 9 d.ceil(); // 10 d.toStringAsFixed(2); // "9.88" d.abs(); // valor absoluto int n = -5; n.abs(); // 5 n.isEven; // false
Os métodos round(), floor() e ceil() arredondam de formas distintas. toStringAsFixed() formata casas decimais.
Controlo de Fluxo
if / else
if (idade >= 18) {
print("adulto");
} else if (idade > 12) {
print("jovem");
} else {
print("criança");
}A estrutura if/else if/else avalia condições em ordem e executa o primeiro bloco verdadeiro. Os parênteses são obrigatórios.
for clássico
for (int i = 0; i < 10; i++) {
print(i);
}
for (int i = 10; i > 0; i--) {
print(i); // contagem decrescente
}O ciclo for tem inicialização, condição e incremento. É ideal quando precisas do índice ou de controlar o contador manualmente.
break e continue
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // salta o 3
if (i == 7) break; // para no 7
print(i);
}
// imprime 0,1,2,4,5,6O continue salta para a próxima iteração e o break termina o ciclo imediatamente. Ambos funcionam em for, while e switch.
if como expressão
// Em coleções (collection-if) var itens = [ "sempre", if (ativo) "ativo", "fim", ]; // Atribuição com ternário String r = ativo ? "sim" : "não";
Dentro de listas podes usar if para incluir elementos condicionalmente. Para expressões simples prefere o operador ternário ? :.
for-in
var frutas = ["maçã", "pera", "uva"];
for (var fruta in frutas) {
print(fruta);
}
for (var i in [1, 2, 3]) {
print(i * 2);
}O for-in percorre cada elemento de uma coleção sem precisar de índice. É a forma mais legível de iterar sobre List, Set ou Map.keys.
Operador ternário
int idade = 20;
String r = idade >= 18 ? "adulto" : "menor";
// Encadeado (evitar em excesso)
String nivel = nota >= 18 ? "A"
: nota >= 14 ? "B"
: "C";O ternário condição ? valorSeTrue : valorSeFalse é um if-else compacto numa expressão. Evita encadear muitos para manter a legibilidade.
switch / case
switch (dia) {
case 1:
print("Segunda");
break;
case 2:
print("Terça");
break;
default:
print("Outro");
}O switch compara um valor com vários case. Cada caso precisa de break (ou return) para não continuar para o seguinte.
while
int x = 5;
while (x > 0) {
print(x);
x--;
}
// x termina em 0O while repete enquanto a condição for verdadeira. Verifica a condição antes de cada iteração, podendo nunca executar o bloco.
Switch expression (Dart 3)
String nome = switch (dia) {
1 => "Segunda",
2 => "Terça",
3 => "Quarta",
_ => "Outro",
};
// Com padrões
var tipo = switch (valor) {
int() => "inteiro",
String() => "texto",
_ => "desconhecido",
};Em Dart 3 o switch pode ser uma expressão que devolve um valor. O _ é o caso padrão e os patterns permitem matching avançado.
do-while
int x = 0;
do {
print(x);
x++;
} while (x < 5);
// executa pelo menos uma vezO do-while executa o bloco antes de verificar a condição, garantindo pelo menos uma execução. Útil para menus e validações.
Funções
Função simples
int somar(int a, int b) {
return a + b;
}
void saudacao(String nome) {
print("Olá $nome");
}
print(somar(2, 3)); // 5Declara o tipo de retorno antes do nome. void indica que não devolve valor. Os parâmetros podem ter tipos explícitos para maior segurança.
Valores por omissão
void conectar({
String host = "localhost",
int porta = 3306,
bool ssl = false,
}) {
print("$host:$porta ssl=$ssl");
}
conectar(porta: 5432);Valores por omissão aplicam-se quando o argumento não é fornecido. Em parâmetros nomeados, os opcionais são automaticamente nullable ou com default.
Funções genéricas
T primeiro<T>(List<T> lista) {
return lista.first;
}
print(primeiro<int>([1, 2, 3])); // 1
print(primeiro<String>(["a", "b"])); // "a"O <T> torna a função genérica, funcionando com qualquer tipo. O tipo pode ser inferido ou especificado explicitamente na chamada.
Arrow function (=>)
int dobro(int x) => x * 2;
bool ePar(int n) => n % 2 == 0;
// Equivalente a:
int triplo(int x) {
return x * 3;
}A sintaxe => expressao é um atalho para funções de uma só expressão que devolvem esse valor. Substitui { return ...; }.
Funções anónimas (lambda)
var lista = [1, 2, 3];
lista.forEach((n) => print(n));
var dobro = (int x) => x * 2;
print(dobro(5)); // 10
var soma = (int a, int b) {
return a + b;
};Funções sem nome podem ser atribuídas a variáveis ou passadas como argumentos. São a base de callbacks e de métodos como map() e where().
Função main
void main() {
print("Início da app");
}
// Com argumentos (CLI)
void main(List<String> args) {
print(args);
}O main() é o ponto de entrada de qualquer programa Dart. Em apps Flutter é onde se chama runApp().
Parâmetros nomeados
void ola({required String nome, int idade = 0}) {
print("$nome tem $idade");
}
ola(nome: "Ana", idade: 30);
ola(nome: "Rui"); // idade = 0Parâmetros entre { } são passados pelo nome, em qualquer ordem. required torna-os obrigatórios e pode haver valores por omissão.
Função como parâmetro
int aplicar(int x, int Function(int) f) {
return f(x);
}
print(aplicar(5, (n) => n * 2)); // 10
print(aplicar(5, (n) => n + 1)); // 6Funções são objetos de primeira classe em Dart. O tipo int Function(int) descreve uma função que recebe e devolve int.
Parâmetro required
class Usuario {
final String nome;
final String email;
Usuario({required this.nome, required this.email});
}
var u = Usuario(nome: "Ana", email: "a@b.com");O required obriga a passar o argumento nomeado, evitando null inesperados. É essencial em construtores de widgets e modelos.
Parâmetros posicionais opcionais
void log(String msg, [int nivel = 0]) {
print("[$nivel] $msg");
}
log("erro", 2); // [2] erro
log("info"); // [0] infoParâmetros entre [ ] são posicionais e opcionais. Devem vir depois dos obrigatórios e podem ter valor por omissão.
Função como retorno
Function multiplicador(int fator) {
return (int x) => x * fator;
}
var dobrar = multiplicador(2);
var triplicar = multiplicador(3);
print(dobrar(5)); // 10
print(triplicar(5)); // 15Uma função pode devolver outra função, criando closures que capturam variáveis do escopo externo (aqui o fator).
Escopo de variáveis
String global = "fora";
void teste() {
String local = "dentro";
print(global); // acessível
print(local);
}
// print(local); // ERRO: fora de escopoVariáveis declaradas dentro de uma função são locais e inacessíveis fora dela. O Dart tem escopo léxico: blocos { } criam novos escopos.
Classes e OOP
Classe básica
class Pessoa {
String nome;
int idade;
Pessoa(this.nome, this.idade);
}
var p = Pessoa("Ana", 30);
print(p.nome); // AnaO construtor Pessoa(this.nome, this.idade) atribui automaticamente os argumentos aos campos. É a forma idiomática e compacta do Dart.
Classe abstrata
abstract class Forma {
double area();
}
class Circulo extends Forma {
final double raio;
Circulo(this.raio);
@override
double area() => 3.14 * raio * raio;
}Uma classe abstract não pode ser instanciada e define um contrato. As subclasses são obrigadas a implementar os métodos abstratos.
Enum avançado (Dart 2.17+)
enum Planeta {
terra(9.8),
marte(3.7),
lua(1.6);
final double gravidade;
const Planeta(this.gravidade);
}
print(Planeta.terra.gravidade); // 9.8Enums em Dart podem ter campos, construtores e métodos. Cada valor passa argumentos ao construtor const, permitindo dados associados.
super parameters (Dart 2.17+)
class Animal {
final String nome;
Animal(this.nome);
}
class Cao extends Animal {
final String raca;
Cao(super.nome, this.raca);
}
var c = Cao("Rex", "Labrador");O super.nome no construtor encaminha automaticamente o argumento para a superclasse, evitando escrever : super(nome).
Construtor nomeado
class Ponto {
final int x, y;
const Ponto(this.x, this.y);
Ponto.origem() : this(0, 0);
Ponto.eixoX(this.x) : y = 0;
}
var p = Ponto.origem();Construtores nomeados como Ponto.origem() oferecem formas alternativas de criar objetos. O : this(...) delega noutro construtor.
Interface (implements)
class Ponto {
int x = 0, y = 0;
}
class Ponto3D implements Ponto {
int x = 0, y = 0, z = 0;
}
void imprimir(Ponto p) => print(p.x);Em Dart qualquer classe pode ser uma interface com implements. A classe deve reimplementar todos os membros da interface.
Extension methods
extension StringExtra on String {
String get invertida => split("").reversed.join();
bool get eNumero => int.tryParse(this) != null;
}
print("abc".invertida); // "cba"
print("123".eNumero); // trueUma extension adiciona métodos a tipos existentes sem os modificar. Acedem-se como se fossem métodos nativos do tipo.
Membros estáticos
class Matematica {
static const double pi = 3.14159;
static double areaCirculo(double r) {
return pi * r * r;
}
}
print(Matematica.pi);
print(Matematica.areaCirculo(2));Membros static pertencem à classe e não à instância. Acedem-se pelo nome da classe, sem criar objeto. Útil para utilitários e constantes.
Getters e Setters
class Retangulo {
double largura = 0, altura = 0;
double get area => largura * altura;
set area(double v) {
largura = altura = v;
}
}
var r = Retangulo();
r.area = 5; // chama o setter
print(r.area); // chama o getterget cria uma propriedade computada de leitura e set permite atribuição com lógica. Acedem-se como campos normais, sem parênteses.
Mixin (with)
mixin Nadador {
void nadar() => print("a nadar");
}
mixin Voador {
void voar() => print("a voar");
}
class Pato extends Animal with Nadador, Voador {}
Pato().nadar();
Pato().voar();Um mixin adiciona comportamento a várias classes sem herança clássica. Aplica-se com with e pode combinar vários mixins.
Classes genéricas
class Caixa<T> {
T valor;
Caixa(this.valor);
}
var c = Caixa<int>(42);
var s = Caixa<String>("olá");
print(c.valor); // 42O <T> torna a classe reutilizável para qualquer tipo. O tipo é fixado na instanciação, garantindo segurança de tipos.
Records (Dart 3)
(String, int) pessoa = ("Ana", 30);
print(pessoa.$1); // Ana
print(pessoa.$2); // 30
// Com nomes
({String nome, int idade}) p = (nome: "Ana", idade: 30);
print(p.nome);Os records agrupam vários valores sem criar uma classe. Podem ser posicionais ($1, $2) ou nomeados.
Herança (extends)
class Animal {
void falar() => print("...");
}
class Cao extends Animal {
@override
void falar() => print("Au au");
}
Cao().falar(); // Au auO extends herda campos e métodos da superclasse. O @override indica que estás a reescrever um método herdado.
Enum simples
enum Estado { ativo, inativo, pendente }
Estado e = Estado.ativo;
print(e.name); // "ativo"
print(e.index); // 0
if (e == Estado.ativo) {
print("ativo!");
}O enum define um conjunto fixo de valores. .name devolve o nome como string e .index a posição (começa em 0).
Operador super
class Animal {
final String nome;
Animal(this.nome);
}
class Cao extends Animal {
final String raca;
Cao(String nome, this.raca) : super(nome);
}
var c = Cao("Rex", "Labrador");O super(...) chama o construtor da superclasse. Em Dart 2.17+ podes usar super.nome diretamente nos parâmetros.
Factory constructor
class Logger {
static final Logger _inst = Logger._();
Logger._();
factory Logger() => _inst;
void log(String msg) => print(msg);
}
var a = Logger();
var b = Logger(); // mesma instânciaUm construtor factory controla a criação do objeto, podendo devolver instâncias existentes (singleton) ou de subclasses.
Coleções
List (lista)
var lista = [1, 2, 3]; lista.add(4); // adiciona lista.remove(1); // remove o valor 1 lista.removeAt(0); // remove pelo índice lista.length; // tamanho lista[0]; // acesso por índice lista.first; lista.last;
A List é uma coleção ordenada e dinâmica. add() insere no fim, remove() elimina por valor e o índice acede aos elementos.
any() e every()
var lista = [1, 2, 3, 4]; lista.any((e) => e > 3); // true (algum) lista.every((e) => e > 0); // true (todos) lista.contains(2); // true lista.isEmpty; // false
O any() verifica se pelo menos um elemento cumpre a condição e every() se todos cumprem.
Collection-if e for
bool ativo = true; var itens = [ "base", if (ativo) "extra", for (var i in [1, 2]) "item$i", ]; // ["base", "extra", "item1", "item2"]
Dentro de literais de coleção podes usar if e for para construir listas dinamicamente. Muito usado no children de widgets.
Map (mapa)
var mapa = {"a": 1, "b": 2};
mapa["c"] = 3; // adiciona/atualiza
mapa.remove("a"); // remove chave
mapa.keys; // ("b", "c")
mapa.values; // (2, 3)
mapa.containsKey("b"); // true
mapa["x"] ??= 0; // só se não existirO Map guarda pares chave-valor. Aceder a uma chave inexistente devolve null. keys e values devolvem iteráveis.
fold() e reduce()
var lista = [1, 2, 3, 4]; int soma = lista.fold(0, (acc, e) => acc + e); // 10 int produto = lista.reduce((acc, e) => acc * e); // 24
O fold() acumula um valor a partir de um inicial. O reduce() faz o mesmo mas usa o primeiro elemento como valor inicial.
List.generate()
var quadrados = List.generate(5, (i) => i * i); // [0, 1, 4, 9, 16] var pares = List.generate(10, (i) => i * 2); // [0, 2, 4, ..., 18]
O List.generate() cria uma lista a partir de uma função que recebe o índice. Ideal para gerar sequências ou dados de teste.
Set (conjunto)
var conjunto = {1, 2, 3};
conjunto.add(3); // ignorado (duplicado)
conjunto.add(4);
conjunto.contains(1); // true
conjunto.length; // 4
var uniao = {1, 2}.union({2, 3}); // {1,2,3}O Set guarda valores únicos sem ordem garantida. Adicionar um duplicado não tem efeito. Útil para eliminar repetições.
sort() e reversed
var lista = [3, 1, 4, 1, 5]; lista.sort(); // [1, 1, 3, 4, 5] lista.sort((a, b) => b.compareTo(a)); // desc var nomes = ["ana", "rui"]; nomes.reversed.toList(); // ["rui", "ana"]
O sort() ordena a lista no lugar (muta a original). Passa um comparador para ordenação personalizada. reversed devolve a ordem inversa.
forEach e indexed
var frutas = ["maçã", "pera"];
frutas.forEach((f) => print(f));
// Com índice (Dart 3)
for (var (i, f) in frutas.indexed) {
print("$i: $f");
}O forEach() executa uma função para cada elemento. O .indexed fornece pares (índice, valor) para iterar com posição.
map() e where()
var lista = [1, 2, 3, 4, 5]; var dobro = lista.map((e) => e * 2).toList(); // [2, 4, 6, 8, 10] var pares = lista.where((e) => e % 2 == 0).toList(); // [2, 4]
O map() transforma cada elemento e o where() filtra. Ambos devolvem um Iterable; usa toList() para materializar.
Spread operator (...)
var a = [1, 2];
var b = [0, ...a, 3]; // [0, 1, 2, 3]
List<int>? nula;
var c = [...?nula, 1]; // spread null-safe
var mapa = {...outroMapa, "x": 1};O ... expande os elementos de uma coleção dentro de outra. O ...? evita erro se a coleção for null.
Map.fromEntries e transformações
var mapa = {"a": 1, "b": 2};
var dobrado = mapa.map((k, v) => MapEntry(k, v * 2));
// {"a": 2, "b": 4}
var lista = mapa.entries.toList();O map() de um Map devolve MapEntry para transformar chaves e valores. entries expõe os pares como iterável.
Async e Streams
async / await
Future<String> buscar() async {
var r = await http.get(url);
return r.body;
}
void main() async {
var dados = await buscar();
print(dados);
}O async marca uma função assíncrona e o await pausa a execução até o Future completar, mantendo o código legível.
Future.wait (paralelo)
var resultados = await Future.wait([ buscarA(), buscarB(), buscarC(), ]); print(resultados); // [a, b, c]
O Future.wait() executa vários Future em paralelo e aguarda todos. Mais rápido do que await sequencial quando são independentes.
onError e catchError
// Com catchError
buscar().catchError((e) {
print("Falhou: $e");
return "fallback";
});
// Stream com onError
stream.listen(
(d) => print(d),
onError: (e) => print(e),
);O catchError() trata erros de um Future e pode devolver um valor alternativo. No listen() usa o parâmetro onError.
Future
Future<String> tarefa() {
return Future.delayed(
Duration(seconds: 2),
() => "pronto",
);
}
tarefa().then((v) => print(v));Um Future<T> representa um valor disponível no futuro. O .then() regista um callback executado quando o resultado estiver pronto.
StreamController
final controller = StreamController<int>(); controller.stream.listen((n) => print(n)); controller.add(1); controller.add(2); await controller.close();
O StreamController permite emitir valores manualmente para uma stream via .add(). O .listen() subscreve os eventos.
async* e yield*
Stream<int> ate(int n) async* {
for (var i = 0; i <= n; i++) {
yield i;
}
}
Stream<int> dobro() async* {
yield* ate(3).map((e) => e * 2);
}O async* devolve uma Stream e o yield emite valores. O yield* reemite todos os valores de outra stream.
try / catch async
try {
var dados = await buscar();
print(dados);
} catch (e) {
print("Erro: $e");
} finally {
print("terminado");
}Erros em código await são capturados com try/catch normal. O finally executa sempre, com ou sem erro.
StreamBuilder (widget)
StreamBuilder<int>(
stream: contar(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("${snapshot.data}");
}
return CircularProgressIndicator();
},
)O StreamBuilder reconstrói a UI sempre que a stream emite um valor. O snapshot contém o estado e os dados atuais.
sync* e generators
Iterable<int> fib(int n) sync* {
int a = 0, b = 1;
for (var i = 0; i < n; i++) {
yield a;
[a, b] = [b, a + b];
}
}
print(fib(6).toList()); // [0,1,1,2,3,5]O sync* cria um gerador síncrono que devolve um Iterable. Os valores são produzidos preguiçosamente (lazy) ao iterar.
Stream
Stream<int> contar() async* {
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
await for (var n in contar()) {
print(n);
}Uma Stream emite vários valores ao longo do tempo. O async* com yield produz os valores e o await for consome-os.
FutureBuilder (widget)
FutureBuilder<String>(
future: buscar(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return Text(snapshot.data ?? "");
}
return CircularProgressIndicator();
},
)O FutureBuilder mostra um estado de loading enquanto o Future não completa e a UI final quando termina. Ideal para pedidos HTTP.
Timeout e retry
try {
var r = await buscar()
.timeout(Duration(seconds: 5));
} on TimeoutException {
print("Demorou demais");
}
// Repetir em streams
stream.timeout(Duration(seconds: 3));O timeout() lança TimeoutException se o Future não completar a tempo. Essencial para pedidos de rede robustos.
Widgets e Layout
StatelessWidget
class Ola extends StatelessWidget {
const Ola({super.key});
@override
Widget build(BuildContext context) {
return Text("Olá Mundo");
}
}Um StatelessWidget é imutável: descreve a UI a partir dos seus parâmetros. O método build() é chamado sempre que o widget precisa de ser redesenhado.
ListView.builder
ListView.builder(
itemCount: itens.length,
itemBuilder: (context, i) {
return ListTile(
title: Text(itens[i]),
);
},
)O ListView.builder cria itens de forma preguiçosa (lazy), construindo apenas os visíveis. Essencial para listas grandes com performance.
Ícones
Icon(Icons.favorite, color: Colors.red, size: 32)
Icon(Icons.add)
IconButton(
icon: Icon(Icons.delete),
onPressed: () {},
)O widget Icon mostra ícones Material do Icons. O IconButton torna o ícone clicável com um callback onPressed.
Card e ListTile
Card(
elevation: 4,
child: ListTile(
leading: Icon(Icons.person),
title: Text("Ana Silva"),
subtitle: Text("Programadora"),
trailing: Icon(Icons.arrow_forward),
onTap: () {},
),
)O Card é uma superfície Material com sombra. O ListTile é uma linha padrão com leading, title, subtitle e trailing.
Spacer e Divider
Row(
children: [
Text("Esquerda"),
Spacer(), // empurra para os lados
Text("Direita"),
],
)
Divider(thickness: 1, color: Colors.grey)O Spacer ocupa todo o espaço livre numa Row/Column. O Divider desenha uma linha separadora horizontal.
Scaffold
Scaffold(
appBar: AppBar(title: Text("Título")),
body: Center(child: Text("Conteúdo")),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
)O Scaffold é a estrutura base de um ecrã Material. Define appBar, body, floatingActionButton, drawer e bottomNavigationBar.
GestureDetector
GestureDetector(
onTap: () => print("toque"),
onDoubleTap: () => print("duplo"),
onLongPress: () => print("longo"),
child: Text("Toca aqui"),
)O GestureDetector deteta gestos como onTap, onDoubleTap e onLongPress em qualquer widget filho.
Stack e Positioned
Stack(
children: [
Image.asset("assets/fundo.png"),
Positioned(
bottom: 10,
right: 10,
child: Text("Legenda"),
),
],
)A Stack sobrepõe widgets em camadas. O Positioned posiciona um filho com coordenadas absolutas dentro da stack.
GridView
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: itens.length,
itemBuilder: (context, i) => Card(),
)O GridView.builder cria uma grelha com scroll. O crossAxisCount define o número de colunas e os spacing os espaçamentos.
SafeArea
SafeArea(
child: Column(
children: [
Text("Conteúdo seguro"),
],
),
)O SafeArea adiciona padding para evitar notch, barra de estado e botões do sistema. Garante que o conteúdo fica em área visível.
Column e Row
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("A"),
Text("B"),
],
)A Column organiza os filhos na vertical e a Row na horizontal. mainAxisAlignment alinha no eixo principal e crossAxisAlignment no transversal.
Text e TextStyle
Text(
"Olá Flutter",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
textAlign: TextAlign.center,
)O Text exibe texto e o TextStyle controla fonte, tamanho, peso e cor. textAlign define o alinhamento horizontal.
Expanded e Flexible
Row(
children: [
Expanded(flex: 2, child: Text("A")),
Expanded(flex: 1, child: Text("B")),
],
)O Expanded faz o filho ocupar o espaço disponível numa Row ou Column. O flex define a proporção entre vários expanded.
Wrap
Wrap(
spacing: 8,
runSpacing: 8,
children: [
Chip(label: Text("Dart")),
Chip(label: Text("Flutter")),
Chip(label: Text("Firebase")),
],
)O Wrap dispõe os filhos em linha e quebra para a linha seguinte quando não cabem. Ideal para tags e chips com largura variável.
Container
Container(
width: 100,
height: 100,
padding: EdgeInsets.all(8),
margin: EdgeInsets.all(16),
color: Colors.blue,
child: Text("Olá"),
)O Container é uma caixa versátil com dimensões, padding, margin, cor e decoração. Combina vários widgets de layout num só.
Imagens
Image.asset("assets/foto.png", width: 200)
Image.network("https://site.com/img.jpg")
Image.file(File("/caminho/foto.png"))
CircleAvatar(
backgroundImage: AssetImage("assets/perfil.png"),
)Image.asset carrega de assets locais, Image.network de uma URL e Image.file do dispositivo. Regista os assets no pubspec.yaml.
Padding e SizedBox
Padding(
padding: EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Text("Olá"),
)
SizedBox(height: 20) // espaçamento
SizedBox(width: 100, child: botao)O Padding adiciona espaço interno com EdgeInsets. O SizedBox cria espaçamentos fixos ou força dimensões num filho.
SingleChildScrollView
SingleChildScrollView(
child: Column(
children: [
Text("A"),
Text("B"),
Text("C"),
],
),
)O SingleChildScrollView adiciona scroll a um único filho. Usado quando o conteúdo pode exceder o ecrã e evitar overflow.
Estado e Navegação
StatefulWidget
class Contador extends StatefulWidget {
const Contador({super.key});
@override
State<Contador> createState() => _ContadorState();
}
class _ContadorState extends State<Contador> {
int n = 0;
@override
Widget build(BuildContext context) {
return Text("$n");
}
}Um StatefulWidget tem estado mutável guardado numa classe State. O createState() liga o widget ao seu estado persistente.
Navegação (push)
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DetalhePage(),
),
);O Navigator.push() empilha um novo ecrã. O MaterialPageRoute define a transição e o builder cria a página de destino.
showDialog
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("Atenção"),
content: Text("Confirma a ação?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("OK"),
),
],
),
);O showDialog() exibe um AlertDialog modal. Os actions contêm botões e Navigator.pop() fecha a caixa.
InheritedWidget / Theme
// Aceder ao tema em qualquer widget Theme.of(context).primaryColor; MediaQuery.of(context).size; // Dados partilhados via InheritedWidget var dados = Dependencia.of(context);
O InheritedWidget partilha dados pela árvore de widgets. Theme.of() e MediaQuery.of() são exemplos clássicos deste padrão.
setState
ElevatedButton(
onPressed: () {
setState(() {
n++;
});
},
child: Text("Incrementar"),
)O setState() atualiza o estado e marca o widget para reconstrução. Só deve ser chamado dentro de uma classe State.
Navegação (pop)
Navigator.pop(context); // Devolver resultado Navigator.pop(context, "valor"); // Receber no push var r = await Navigator.push(context, rota);
O Navigator.pop() fecha o ecrã atual. Pode devolver um resultado que é recebido pelo await do push() que o abriu.
showModalBottomSheet
showModalBottomSheet(
context: context,
builder: (_) => Container(
height: 200,
child: Center(child: Text("Opções")),
),
);O showModalBottomSheet() abre um painel a partir do fundo do ecrã. Muito usado para menus de ações e seleções.
ValueNotifier e ValueListenableBuilder
final contador = ValueNotifier<int>(0);
ValueListenableBuilder<int>(
valueListenable: contador,
builder: (context, valor, _) {
return Text("$valor");
},
)
contador.value++; // atualiza a UIO ValueNotifier guarda um valor notificável. O ValueListenableBuilder reconstrói só a parte afetada quando o .value muda, sem setState.
Ciclo de vida (State)
class _PaginaState extends State<Pagina> {
@override
void initState() {
super.initState(); // 1º: inicialização
}
@override
void dispose() {
super.dispose(); // último: limpeza
}
}O initState() executa uma vez na criação (ideal para subscriptions) e o dispose() na destruição (liberta recursos e controllers).
Rotas nomeadas
MaterialApp(
initialRoute: "/",
routes: {
"/": (_) => Home(),
"/detalhe": (_) => Detalhe(),
},
)
Navigator.pushNamed(context, "/detalhe");Define rotas no routes do MaterialApp e navega por nome com pushNamed(). Mais organizado para apps com muitos ecrãs.
SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Guardado!"),
duration: Duration(seconds: 2),
action: SnackBarAction(
label: "Desfazer",
onPressed: () {},
),
),
);O SnackBar mostra uma mensagem temporária em baixo. Exibe-se via ScaffoldMessenger e pode ter uma SnackBarAction.
didUpdateWidget
@override
void didUpdateWidget(covariant MeuWidget old) {
super.didUpdateWidget(old);
if (old.id != widget.id) {
recarregar();
}
}O didUpdateWidget() é chamado quando o widget pai muda e o State é reutilizado. Compara o widget antigo com o novo via widget.
Passar argumentos
// Enviar
Navigator.pushNamed(
context, "/detalhe",
arguments: {"id": 42},
);
// Receber
final args = ModalRoute.of(context)!
.settings.arguments as Map;O arguments do pushNamed() envia dados para a rota. Na página de destino obtêm-se via ModalRoute.of(context).settings.arguments.
pushReplacement
Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => Login()), ); // Limpar toda a pilha Navigator.pushAndRemoveUntil( context, rota, (r) => false, );
O pushReplacement() substitui o ecrã atual (sem voltar). O pushAndRemoveUntil() empilha e remove ecrãs anteriores segundo um predicado.
CLI, Temas e Boas Práticas
Criar e correr projeto
flutter create minha_app cd minha_app flutter run # Correr num dispositivo específico flutter run -d chrome flutter devices
O flutter create gera a estrutura do projeto e o flutter run compila e executa com hot reload. -d escolhe o dispositivo.
Theme e cores
MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
),
)
// Aceder em qualquer widget
Theme.of(context).primaryColor;
Color(0xFF6200EE);
Colors.blue.shade700;O ThemeData define cores e estilos globais. Theme.of(context) acede ao tema. Cores usam Colors ou hexadecimal Color(0xFF...).
Estrutura de pastas
lib/ main.dart models/ # classes de dados services/ # API, lógica screens/ # páginas widgets/ # widgets reutilizáveis theme/ # estilos
Organiza o código em models, services, screens e widgets. Separa a UI da lógica de negócio para manutenção.
Comandos CLI úteis
flutter pub get # instala dependências flutter pub add http # adiciona pacote flutter clean # limpa build flutter build apk # gera APK flutter build appbundle flutter doctor # verifica ambiente
O flutter pub gere dependências. flutter clean resolve problemas de cache e flutter doctor diagnostica a instalação.
MediaQuery (dimensões)
final size = MediaQuery.of(context).size;
double largura = size.width;
double altura = size.height;
double padding = MediaQuery.of(context)
.padding.top; // barra de estadoO MediaQuery fornece dimensões do ecrã e informações do dispositivo. Essencial para layouts responsivos e adaptar a UI.
Boas práticas
// 1. Widgets pequenos e reutilizáveis // 2. const sempre que possível // 3. Separar UI da lógica // 4. Evitar setState em excesso // 5. Libertar controllers no dispose()
Prefere widgets pequenos, usa const, separa a lógica da UI e liberta recursos no dispose(). Evita reconstruções desnecessárias.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
http: ^1.2.0
provider: ^6.1.0
flutter:
assets:
- assets/
- assets/imagens/O pubspec.yaml declara dependências e assets. Após alterar, corre flutter pub get. O ^ aceita atualizações compatíveis.
LayoutBuilder (responsivo)
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return Text("Ecrã largo");
}
return Text("Ecrã estreito");
},
)O LayoutBuilder dá acesso às constraints do pai, permitindo adaptar o layout ao espaço disponível (responsividade).
Debug e print
print("valor: $x");
debugPrint("texto longo...");
// Flutter DevTools (inspetor de widgets)
flutter run
# abre o DevTools no terminal
assert(x > 0, "x deve ser positivo");Usa print() para logs simples e debugPrint() para textos longos. O Flutter DevTools inspeciona widgets, performance e rede.
Hot reload e restart
# Durante flutter run: r # hot reload (rápido, mantém estado) R # hot restart (reinicia estado) q # sair o # alternar plataforma
O hot reload (r) aplica mudanças de UI mantendo o estado. O hot restart (R) reinicia o estado da app por completo.
const (performance)
// Widget constante (não reconstrói)
const Text("Olá")
const SizedBox(height: 10)
const Icon(Icons.star)
// Construtor const
class Titulo extends StatelessWidget {
const Titulo({super.key});
}Marca widgets imutáveis com const para o Flutter os reutilizar e evitar reconstruções desnecessárias, melhorando a performance.
Assets e fontes
# pubspec.yaml
flutter:
assets:
- assets/logo.png
fonts:
- family: Roboto
fonts:
- asset: fonts/Roboto.ttf
// Uso
Image.asset("assets/logo.png")
TextStyle(fontFamily: "Roboto")Regista assets e fontes no pubspec.yaml para os incluir no build. Acede com Image.asset() e fontFamily no TextStyle.
Formulários e Input
TextField básico
TextField(
decoration: InputDecoration(
labelText: "Nome",
hintText: "Escreve o teu nome",
border: OutlineInputBorder(),
),
onChanged: (texto) => print(texto),
)O TextField recebe texto do utilizador. O InputDecoration define label, hint e borda. O onChanged dispara a cada alteração.
Teclado e foco
TextField( keyboardType: TextInputType.number, obscureText: true, // password textInputAction: TextInputAction.next, autofocus: true, ) // Esconder teclado FocusScope.of(context).unfocus();
O keyboardType escolhe o teclado, obscureText oculta caracteres (senha) e textInputAction define o botão de ação. unfocus() fecha o teclado.
DropdownButton
String? pais;
DropdownButton<String>(
value: pais,
hint: Text("Escolhe um país"),
items: ["Portugal", "Brasil"]
.map((p) => DropdownMenuItem(
value: p,
child: Text(p),
))
.toList(),
onChanged: (v) => setState(() => pais = v),
)O DropdownButton mostra uma lista suspensa. Os items são DropdownMenuItem e o onChanged recebe o valor selecionado.
TextEditingController
final controller = TextEditingController();
TextField(controller: controller);
// Ler e definir valor
print(controller.text);
controller.text = "novo valor";
controller.clear();
@override
void dispose() {
controller.dispose();
super.dispose();
}O TextEditingController permite ler e controlar o texto programaticamente. Deve ser libertado no dispose() para evitar memory leaks.
Checkbox
bool aceite = false;
CheckboxListTile(
title: Text("Aceito os termos"),
value: aceite,
onChanged: (v) {
setState(() => aceite = v ?? false);
},
)O Checkbox alterna um bool. O CheckboxListTile junta a caixa com um texto. Atualiza o estado no onChanged.
Slider
double volume = 0.5; Slider( value: volume, min: 0, max: 1, divisions: 10, label: volume.toStringAsFixed(1), onChanged: (v) => setState(() => volume = v), )
O Slider seleciona um valor num intervalo. min/max definem os limites, divisions os passos e label o valor mostrado.
Form e validação
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(children: [
TextFormField(
validator: (v) =>
v!.isEmpty ? "Obrigatório" : null,
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// válido
}
},
child: Text("Enviar"),
),
]),
)O Form agrupa campos com uma GlobalKey<FormState>. O validator devolve uma mensagem de erro ou null se válido. validate() verifica todos.
Radio
String opcao = "a";
RadioListTile(
title: Text("Opção A"),
value: "a",
groupValue: opcao,
onChanged: (v) => setState(() => opcao = v!),
)O Radio seleciona uma opção num grupo. O groupValue é o valor atual e o value o deste botão. Só um fica ativo por grupo.
DatePicker
final data = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); if (data != null) print(data);
O showDatePicker() abre um seletor de data e devolve um DateTime?. Define initialDate, firstDate e lastDate.
TextFormField
TextFormField(
decoration: InputDecoration(labelText: "Email"),
keyboardType: TextInputType.emailAddress,
validator: (v) {
if (v == null || !v.contains("@")) {
return "Email inválido";
}
return null;
},
)O TextFormField é um TextField integrado num Form, com validator. O keyboardType ajusta o teclado (email, número, etc.).
Switch
bool ativo = true;
SwitchListTile(
title: Text("Notificações"),
value: ativo,
onChanged: (v) {
setState(() => ativo = v);
},
)O Switch é um interruptor ligado/desligado. O SwitchListTile combina-o com um rótulo. Atualiza o bool no onChanged.
FocusNode
final foco = FocusNode();
TextField(focusNode: foco);
// Dar foco programaticamente
foco.requestFocus();
@override
void dispose() {
foco.dispose();
super.dispose();
}O FocusNode controla o foco de um campo. requestFocus() ativa-o programaticamente. Liberta-o no dispose().