Cheatsheet Dart
Linguagem da Google, base do Flutter, para apps mobile, web e servidor
Dart
Básico e Variáveis
Função main
void main() {
print("Olá, Dart!");
}
// Com argumentos (CLI)
void main(List<String> args) {
print(args);
}O main() é o ponto de entrada de todo o programa Dart. Em scripts CLI pode receber args como List<String>.
Interpolação de strings
String nome = "Ana";
int idade = 30;
print("Olá, $nome");
print("Total: ${preco * 2}");
print("Maiúsculo: ${nome.toUpperCase()}");Usa $variavel para valores simples e ${expressao} para expressões ou chamadas de método dentro da string.
Operadores de comparação
5 == 5; // true igual 5 != 3; // true diferente 5 > 3; // true maior 5 <= 5; // true menor ou igual // idêntico (mesmo valor e tipo) 1 == 1.0; // true (== compara valor)
O == compara valores. Os operadores >, <, >= e <= comparam ordem. != é a negação de igualdade.
Variáveis
var nome = "Ana"; // tipo inferido (String) String cidade = "Porto"; int idade = 30; double preco = 9.99; bool ativo = true;
Usa var para o Dart inferir o tipo, ou declara o tipo explicitamente. Uma variável tipada não aceita valores de outro tipo.
Strings multi-linha e raw
var texto = """ Primeira linha Segunda linha """; var raw = r"Sem \n escape aqui"; var caminho = r"C:\pasta\ficheiro";
Três aspas """ criam strings multi-linha. O prefixo r cria uma string raw, ignorando escapes como \n.
Operadores lógicos
true && false; // false (E)
true || false; // true (OU)
!true; // false (negação)
// curto-circuito
if (a != null && a.length > 0) {
// só avalia a.length se a não for null
}&& (E) e || (OU) são de curto-circuito: o segundo operando só é avaliado se necessário. ! nega um bool.
final e const
final nome = "Ana"; // uma atribuição (runtime) const pi = 3.14; // constante de compilação final lista = [1, 2]; lista.add(3); // OK: conteúdo muda // lista = []; // ERRO: reatribuição
final permite uma única atribuição em runtime. const é imutável desde a compilação e exige valor conhecido estaticamente.
Comentários
// comentário de uma linha
/* comentário
de várias linhas */
/// Documentação (gera docs com dartdoc)
/// [nome] é o parâmetro.
void funcao() {}Usa // para linha, /* */ para bloco e /// para documentação. Os comentários doc geram API docs com a ferramenta dartdoc.
Cascade (..)
var lista = []
..add(1)
..add(2)
..add(3)
..sort();
var sb = StringBuffer()
..write("Olá ")
..write("Dart");O operador .. encadeia várias operações no mesmo objeto sem repetir o nome. Ideal para configurar objetos de forma compacta.
dynamic e Object
dynamic x = "texto"; x = 42; // OK: qualquer tipo x.metodoInexistente(); // compila, falha em runtime Object o = 10; // o.toUpperCase(); // ERRO: Object não tem
dynamic desativa a verificação de tipos (risco em runtime). Object é a raiz de todos os tipos, mas mantém a segurança estática.
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
A divisão / devolve sempre double. Usa ~/ para o quociente inteiro e % para o resto da divisão.
Incremento e atribuição
int x = 5; x++; // 6 (pós-incremento) ++x; // 7 (pré-incremento) x += 3; // x = x + 3 x *= 2; // x = x * 2 x ??= 0; // atribui só se for null
Os operadores compostos +=, -=, *= combinam operação e atribuição. ??= atribui apenas se a variável for null.
Tipos de Dados
int e double
int x = 10; // inteiro double y = 3.14; // decimal num z = 5; // int ou double num w = 2.5; // também válido int hex = 0xFF; // hexadecimal int bin = 0b1010; // binário (10)
int é inteiro e double decimal. num é a superclasse de ambos. Suporta notação hexadecimal 0x e binária 0b.
String: split e replace
String s = "a,b,c,d";
s.split(","); // ["a","b","c","d"]
s.replaceAll(",", "-"); // "a-b-c-d"
s.replaceFirst(",", ";"); // "a;b,c,d"
s.substring(0, 3); // "a,b"
s.indexOf(","); // 1split() devolve uma List<String>. replaceAll() substitui todas as ocorrências e substring() extrai parte da string.
List (lista)
List<int> nums = [1, 2, 3]; nums.add(4); nums[0]; // 1 nums.length; // 4 nums.first; // 1 nums.last; // 4 List vazia = [];
A List é ordenada e dinâmica. O tipo genérico List<int> restringe os elementos. first e last acedem às extremidades.
Métodos numéricos
double d = 9.876; d.round(); // 10 d.floor(); // 9 d.ceil(); // 10 d.toStringAsFixed(2); // "9.88" d.abs(); // valor absoluto d.truncate(); // 9 (corta decimais)
round(), floor() e ceil() arredondam de formas distintas. toStringAsFixed() formata as casas decimais como string.
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"); // nullint.parse() e double.parse() convertem strings em números e lançam exceção se inválidas. tryParse() devolve null em vez de falhar.
Map (mapa)
Map<String, int> idades = {
"Ana": 30,
"Rui": 25,
};
idades["Ana"]; // 30
idades["Zé"] = 40; // adiciona
idades.keys; // (Ana, Rui, Zé)
idades.values; // (30, 25, 40)O Map guarda pares chave-valor. Aceder a uma chave inexistente devolve null. keys e values devolvem iteráveis.
Propriedades numéricas
int n = 10; n.isEven; // true (par) n.isOdd; // false (ímpar) n.isNegative; // false double d = 3.14; d.isFinite; // true d.isNaN; // false
As propriedades isEven/isOdd verificam paridade. isFinite e isNaN são úteis para validar resultados de cálculos.
Booleanos
bool ativo = true;
bool vazio = lista.isEmpty;
bool maior = idade >= 18;
// Dart exige bool real (sem truthy/falsy)
if (ativo) { }
// if (1) { } // ERRO: int não é boolO Dart é estrito: condições exigem um bool real, ao contrário de JavaScript. Não há valores "truthy" — if (1) é erro de compilação.
Set (conjunto)
Set<int> unicos = {1, 2, 2, 3};
// {1, 2, 3} — duplicados removidos
unicos.add(3); // ignorado
unicos.contains(1); // true
unicos.length; // 3O Set guarda valores únicos sem ordem garantida. Adicionar um duplicado não tem efeito. Útil para eliminar repetições de uma lista.
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.startsWith("Olá"); // false (espaços)As String são imutáveis: cada método devolve uma nova string. trim() remove espaços nas pontas e contains() verifica substrings.
Runes e caracteres
String s = "Olá";
s.codeUnits; // códigos UTF-16
s.runes; // code points Unicode
var emoji = "\u{1F600}"; // 😀
print(emoji);runes dá acesso aos code points Unicode (importante para emojis). \u{...} insere um caractere Unicode pelo código.
Verificação de tipo (is / as)
Object x = "texto";
if (x is String) {
print(x.length); // x é promovido a String
}
num n = 5;
int i = n as int; // cast explícitoO is verifica o tipo e promove a variável dentro do bloco. O as faz cast explícito e lança exceção se o tipo não corresponder.
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.
Operador ternário
int idade = 20;
String r = idade >= 18 ? "Adulto" : "Menor";
// Encadeado (usar com moderação)
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.
for-in
var frutas = ["maçã", "pera", "uva"];
for (var fruta in frutas) {
print(fruta);
}
for (var chave in mapa.keys) {
print("$chave: ${mapa[chave]}");
}O for-in percorre cada elemento de uma coleção sem índice. É a forma mais legível de iterar sobre List, Set ou Map.keys.
Labels em ciclos
externo:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break externo; // sai do ciclo externo
}
}
}Um label (nome seguido de :) permite que break ou continue atuem num ciclo externo em loops aninhados.
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.
Pattern matching (Dart 3)
var (ano, mes, dia) = (2024, 7, 24);
print("$dia/$mes/$ano");
// Destruturar lista
var [primeiro, segundo, ...resto] = [1, 2, 3, 4];
// Guard em switch
var r = switch (valor) {
int n when n > 0 => "positivo",
_ => "outro",
};Dart 3 introduz patterns: desestruturação de records e listas, e when para guardas condicionais no switch.
Switch expression (Dart 3)
String nome = switch (dia) {
1 => "Segunda",
2 => "Terça",
3 => "Quarta",
_ => "Outro",
};Em Dart 3 o switch pode ser uma expressão que devolve um valor. O _ é o caso padrão (wildcard) e substitui o default.
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.
if-case (Dart 3)
var json = {"tipo": "circulo", "raio": 5};
if (json case {"tipo": "circulo", "raio": var r}) {
print("Círculo com raio $r");
}
// Padrões em declarações
var [a, b] = [1, 2];O if-case combina condição com pattern matching, extraindo valores de mapas e objetos de forma declarativa e segura.
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 tipados aumentam a segurança do código.
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().
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 e configurações.
Função arrow (=>)
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çã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.
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 devem ser constantes (const).
Parâmetros nomeados
void ligar({required String numero,
bool vivaVoz = false}) {
print("$numero vivaVoz=$vivaVoz");
}
ligar(numero: "123", vivaVoz: true);
ligar(numero: "456"); // vivaVoz = falseParâ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 retorno (closure)
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 uma closure que captura 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.
Parâmetros posicionais opcionais
void saudar(String nome,
[String titulo = "Sr."]) {
print("Olá, $titulo $nome");
}
saudar("Ana"); // Sr. Ana
saudar("Ana", "Dra."); // Dra. AnaParâmetros entre [ ] são posicionais e opcionais. Devem vir depois dos obrigatórios e podem ter valor por omissão.
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.
Tear-off e referência de método
var lista = [1, 2, 3]; // Tear-off: referência ao método lista.forEach(print); // Equivalente a: lista.forEach((n) => print(n)); var dobro = [1, 2].map((n) => n * 2);
Um tear-off é passar um método diretamente (ex.: print) em vez de o envolver numa lambda. Mais limpo quando a assinatura coincide.
Coleções
Operações de List
var lista = [1, 2, 3]; lista.add(4); // adiciona no fim lista.remove(1); // remove o valor 1 lista.removeAt(0); // remove pelo índice lista.insert(0, 9); // insere na posição lista.contains(2); // true lista.indexOf(3); // posição
A List é dinâmica: add() insere no fim, remove() elimina por valor, insert() coloca numa posição específica.
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 a 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: transformações
var mapa = {"a": 1, "b": 2};
var dobrado = mapa.map((k, v) => MapEntry(k, v * 2));
// {"a": 2, "b": 4}
mapa.containsKey("a"); // true
mapa.remove("b"); // remove chaveO map() de um Map devolve MapEntry para transformar chaves e valores. containsKey() verifica a existência de uma chave.
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, sem lógica externa.
Set: operações de conjunto
var a = {1, 2, 3};
var b = {2, 3, 4};
a.union(b); // {1,2,3,4}
a.intersection(b); // {2,3}
a.difference(b); // {1}
a.containsAll([1,2]); // trueO Set oferece operações matemáticas: union() (união), intersection() (interseção) e difference() (diferença).
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 valor inicial. O reduce() faz o mesmo mas usa o primeiro elemento como ponto de partida.
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.
Coleções imutáveis
var fixa = const [1, 2, 3]; // fixa.add(4); // ERRO: imutável var imutavel = List.unmodifiable([1, 2, 3]); // imutavel[0] = 9; // ERRO em runtime var soLeitura = [1, 2, 3] as List<int>;
Uma lista const é imutável em compilação. List.unmodifiable() cria uma vista só de leitura que lança erro ao tentar modificar.
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
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.
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.
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.
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.
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; // setter
print(r.area); // 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.
Classes imutáveis
class Ponto {
final int x;
final int y;
const Ponto(this.x, this.y);
}
const a = Ponto(1, 2);
const b = Ponto(1, 2);
print(identical(a, b)); // true (canonical)Campos final + construtor const criam objetos imutáveis. Instâncias const idênticas são canónicas (partilhadas em memória).
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).
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).
Operador == e hashCode
class Ponto {
final int x, y;
const Ponto(this.x, this.y);
@override
bool operator ==(Object o) =>
o is Ponto && o.x == x && o.y == y;
@override
int get hashCode => Object.hash(x, y);
}Para comparar objetos por valor, sobrescreve operator == e hashCode. Object.hash() gera um hash consistente dos campos.
Assíncrono
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.
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.
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.
sync* (generator)
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.
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.
listen e cancelamento
var sub = stream.listen(
(dado) => print(dado),
onError: (e) => print("Erro: $e"),
onDone: () => print("fim"),
);
// Mais tarde:
await sub.cancel();O listen() subscreve uma stream com callbacks de dados, erro e fim. Guarda a StreamSubscription para cancelar com .cancel().
Timeout
try {
var r = await buscar()
.timeout(Duration(seconds: 5));
} on TimeoutException {
print("Demorou demais");
}O timeout() lança TimeoutException se o Future não completar a tempo. Essencial para pedidos de rede robustos.
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.
then / catchError
buscar()
.then((v) => print(v))
.catchError((e) {
print("Falhou: $e");
return "fallback";
})
.whenComplete(() => print("fim"));O then() encadeia o sucesso, catchError() trata erros (podendo devolver fallback) e whenComplete() executa sempre no fim.
Isolates (paralelismo)
import 'dart:isolate';
int calculoPesado(int n) {
var soma = 0;
for (var i = 0; i < n; i++) soma += i;
return soma;
}
// Corre noutro isolate (thread)
var resultado = await Isolate.run(
() => calculoPesado(1000000),
);O Dart é single-thread por isolate. Isolate.run() executa trabalho pesado noutro isolate, evitando bloquear a UI. Cada isolate tem memória própria.
Null Safety
Tipos nullable (?)
String? nome; // aceita null nome = null; // OK String s = "x"; // s = null; // ERRO: não-nullable int? numero; // nullable int idade = 0; // nunca null
Por defeito os tipos não aceitam null. O sufixo ? torna o tipo nullable, permitindo atribuir null com segurança.
required
class User {
final String email;
final String? telefone;
User({required this.email, this.telefone});
}
var u = User(email: "a@b.com");
// User(); // ERRO: email obrigatórioO required torna um parâmetro nomeado obrigatório, evitando null inesperados. Parâmetros sem ele devem ser nullable ou ter default.
Null-aware em cascade (..?)
StringBuffer? sb = obterBuffer();
sb?..write("Olá")
..write(" Dart");
// só executa se sb != null
lista?..add(1)..sort();O ?.. combina null-aware com cascade: a cadeia só executa se o objeto não for null, evitando exceções em configurações opcionais.
Operador ?? (fallback)
String? input; var nome = input ?? "Anónimo"; // "Anónimo" se input for null int? tentativas; tentativas ??= 0; // atribui só se null
O ?? devolve o operando esquerdo se não for null, senão o direito. O ??= atribui um valor apenas se a variável for null.
late (inicialização tardia)
class Servico {
late final ApiClient api;
Servico() {
api = ApiClient(); // depois do construtor
}
}
late String config = carregarConfig();O late permite declarar uma variável non-null inicializada mais tarde. O Dart só verifica a atribuição no primeiro acesso à variável.
Retorno nullable
int? encontrarIndice(List l, Object v) {
int i = l.indexOf(v);
return i >= 0 ? i : null;
}
var idx = encontrarIndice([1, 2], 2);
if (idx != null) print("posição $idx");Funções que podem falhar devolvem um tipo nullable (int?). O chamador verifica com != null antes de usar o resultado.
Operador ?. (acesso seguro)
String? nome; int? len = nome?.length; // null, sem erro // Em cadeia var cidade = usuario?.endereco?.cidade; // Com método lista?.add(1); // só chama se lista != null
O ?. interrompe a cadeia e devolve null em vez de lançar exceção se o objeto for null. Ideal para acessos encadeados.
Promoção de tipo (flow analysis)
String? nome = obterNome();
if (nome != null) {
// aqui nome é promovido a String
print(nome.length); // sem !
}
// Também com is
Object x = "texto";
if (x is String) print(x.length);Após verificar != null ou is, o Dart promove a variável para o tipo non-null dentro do bloco, dispensando o operador !.
Operador ! (forçar non-null)
String? nome = "Ana"; int len = nome!.length; // afirma não-null // CUIDADO: lança erro se for null String? nulo; // nulo!.length; // Null check operator error
O ! afirma ao compilador que o valor não é null. Se estiver errado, lança erro em runtime. Usa só quando tens a certeza.
Coleções nullable
List<int>? lista;
lista?.add(1); // seguro
var l = lista ?? []; // fallback
Map<String, int?> mapa = {
"a": 1,
"b": null, // valor nullable
};Uma coleção pode ser nullable (List?) ou ter elementos nullable (List<int?>). Usa ?. e ?? conforme o caso.
Genéricos, Records e Patterns
Genéricos em classes
class Pilha<T> {
final List<T> _itens = [];
void push(T item) => _itens.add(item);
T pop() => _itens.removeLast();
bool get vazia => _itens.isEmpty;
}
var p = Pilha<int>();
p.push(1);Genéricos (<T>) criam classes reutilizáveis com segurança de tipos. O tipo é fixado na instanciação, evitando casts em runtime.
Destruturação (destructuring)
var lista = [1, 2, 3, 4];
var [a, b, ...resto] = lista;
// a=1, b=2, resto=[3,4]
var mapa = {"x": 10, "y": 20};
var {"x": x, "y": y} = mapa;Dart 3 permite desestruturar listas e mapas em variáveis individuais. O ...resto captura os elementos restantes numa nova lista.
Parâmetros de tipo múltiplos
Map<K, V> inverter<K, V>(Map<V, K> mapa) {
return mapa.map((k, v) => MapEntry(v, k));
}
var original = {1: "um", 2: "dois"};
var invertido = inverter(original);
// {"um": 1, "dois": 2}Funções podem ter vários parâmetros de tipo (<K, V>). O compilador infere-os a partir dos argumentos passados.
Bibliotecas e imports
import 'dart:math'; import 'package:http/http.dart' as http; import 'utils.dart' show formatar; import 'outra.dart' hide interno; export 'modelo.dart';
import traz bibliotecas; as cria um prefixo, show/hide filtram símbolos. export reexporta de outra biblioteca.
Restrições genéricas (bounds)
T maior<T extends Comparable<T>>(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
print(maior(5, 10)); // 10
print(maior("a", "z")); // "z"T extends Comparable<T> restringe o genérico a tipos comparáveis. O compilador garante que compareTo() existe.
Switch com patterns
String descrever(Object valor) => switch (valor) {
int n => "inteiro $n",
String s => "texto $s",
[1, 2] => "lista [1,2]",
{ "nome": var n } => "mapa nome=$n",
_ => "outro",
};O switch com patterns faz matching por tipo, valor e estrutura. Extrai valores diretamente para variáveis (ex.: int n, var n).
Object vs dynamic vs void
Object a = 1; // qualquer tipo, seguro dynamic b = 1; // sem verificação (risco) void c = funcao(); // não usar o valor // a.metodoX(); // ERRO em compilação b.metodoX(); // compila, falha em runtime
Object é a raiz tipada (segura), dynamic desativa verificações e void indica que o valor não deve ser usado.
Partes e extensão de bibliotecas
// lib.dart
library minha_lib;
part 'parte_a.dart';
part 'parte_b.dart';
// parte_a.dart
part of 'lib.dart';
void funcaoA() {}part e part of dividem uma biblioteca em vários ficheiros que partilham o mesmo escopo. Útil para organizar código extenso.
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, e são imutáveis.
Guardas (when)
String classificar(int n) => switch (n) {
< 0 => "negativo",
0 => "zero",
> 0 && < 10 => "pequeno",
_ => "grande",
};
// Guarda com when
var r = switch (valor) {
int x when x.isEven => "par",
_ => "ímpar",
};Os relational patterns (<, >) e a guarda when permitem condições refinadas dentro dos casos do switch.
Constantes em tempo de compilação
const lista = [1, 2, 3];
const mapa = {"a": 1};
const ponto = Ponto(1, 2); // construtor const
// Deep const: tudo é imutável
const aninhado = [1, [2, 3]];
// aninhado[1].add(4); // ERROO const cria valores imutáveis em compilação, incluindo coleções aninhadas (deep const). Reduz alocações e melhora a performance.
Records como retorno
(double, double) dividir(int a, int b) {
return (a / b, a % b.toDouble());
}
var (quociente, resto) = dividir(10, 3);
print("$quociente resto $resto");Funções podem devolver vários valores via records, sem criar uma classe. A desestruturação var (a, b) = ... extrai os valores.
typedef (type aliases)
typedef Callback = void Function(String);
typedef Comparador<T> = int Function(T, T);
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 e genéricos reutilizáveis.
assert
void definirIdade(int idade) {
assert(idade >= 0, "Idade inválida");
// só ativo em modo debug
}
assert(lista.isNotEmpty);
assert(x > 0, "x deve ser positivo");O assert verifica uma condição em modo debug e lança AssertionError se falsa. É ignorado em produção (não afeta performance).
Exceções e Erros
try / catch
try {
int r = 10 ~/ 0;
} catch (e) {
print("Erro: $e");
}
// Capturar tipo específico
try {
arriscada();
} on FormatException {
print("formato inválido");
}O try/catch captura exceções em runtime. O on filtra por tipo de exceção, permitindo tratamento específico para cada caso.
Exceção personalizada
class SaldoInsuficiente implements Exception {
final double valor;
SaldoInsuficiente(this.valor);
@override
String toString() =>
"Saldo insuficiente: $valor";
}
throw SaldoInsuficiente(100.0);Cria classes que implementam Exception para erros específicos do domínio. Sobrescreve toString() para mensagens claras.
tryParse (evitar exceções)
// Em vez de try/catch:
int? n = int.tryParse("abc"); // null
if (n != null) {
print("número: $n");
}
double? d = double.tryParse("3.14");Métodos tryParse() devolvem null em vez de lançar exceção. Preferível para validação de input, mais limpo e eficiente.
finally
try {
var dados = await lerFicheiro();
processar(dados);
} catch (e) {
print("Falhou: $e");
} finally {
fecharRecursos(); // executa sempre
}O bloco finally executa sempre, haja ou não exceção. Ideal para libertar recursos como ficheiros, ligações ou subscriptions.
Capturar stack trace
try {
operacao();
} catch (e, stack) {
print("Erro: $e");
print("Stack: $stack");
}O segundo parâmetro do catch (stack) é a StackTrace, útil para depurar onde a exceção ocorreu.
Erros vs Exceções
// Error: bug de programação (não recuperar)
// StateError, ArgumentError, RangeError
// Exception: condição esperada (recuperável)
// FormatException, IOException, HttpException
throw StateError("estado inválido");
throw FormatException("input mau");Error indica bugs (não devem ser capturados normalmente). Exception representa condições esperadas e recuperáveis do programa.
throw (lançar exceção)
void definirIdade(int idade) {
if (idade < 0) {
throw ArgumentError("Idade inválida: $idade");
}
}
void guardar(Object? v) {
if (v == null) throw Exception("vazio");
}O throw lança uma exceção. Podes usar tipos nativos como ArgumentError ou Exception, ou criar os teus próprios.
Exceções comuns
int.parse("abc"); // FormatException
[1,2][5]; // RangeError
null!.length; // TypeError (null check)
10 ~/ 0; // UnsupportedError
{}["x"]!; // null check operatorExceções frequentes: FormatException (parse inválido), RangeError (índice fora), TypeError (tipo/null errado).
rethrow
try {
operacao();
} catch (e) {
print("registar: $e");
rethrow; // re-lança a mesma exceção
}O rethrow re-lança a exceção atual preservando a stack trace original. Útil para registar o erro antes de o propagar.
runZonedGuarded
import 'dart:async';
runZonedGuarded(() {
// código que pode lançar erros
operacaoAssincrona();
}, (erro, stack) {
print("Erro global: $erro");
});O runZonedGuarded() captura erros não tratados dentro de uma zona, incluindo os assíncronos. Útil como handler global de erros.