DevTools

Cheatsheet Flutter

Framework para desenvolvimento mobile

Voltar às linguagens
Flutter
132 cards encontrados
Categorias:
Versões:

Sintaxe Básica (Dart)


14 cards
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"); // substitui

As 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};  // conjunto

O 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


10 cards
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,6

O 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 0

O 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 vez

O 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


12 cards
Função simples
int somar(int a, int b) {
  return a + b;
}

void saudacao(String nome) {
  print("Olá $nome");
}

print(somar(2, 3)); // 5

Declara 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 = 0

Parâ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)); // 6

Funçõ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] info

Parâ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)); // 15

Uma 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 escopo

Variá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


16 cards
Classe básica
class Pessoa {
  String nome;
  int idade;

  Pessoa(this.nome, this.idade);
}

var p = Pessoa("Ana", 30);
print(p.nome); // Ana

O 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.8

Enums 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);   // true

Uma 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 getter

get 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); // 42

O <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 au

O 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ância

Um construtor factory controla a criação do objeto, podendo devolver instâncias existentes (singleton) ou de subclasses.

Coleções


12 cards
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 existir

O 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


12 cards
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


18 cards
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


14 cards
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 UI

O 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


12 cards
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 estado

O 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


12 cards
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().