DevTools

Cheatsheet Dart

Linguagem da Google, base do Flutter, para apps mobile, web e servidor

Voltar às linguagens
Dart
122 cards encontrados
Categorias:
Versões:

Básico e Variáveis


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


12 cards
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(",");          // 1

split() 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"); // null

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.

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 é bool

O 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;        // 3

O 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ícito

O 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


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

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 0

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

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


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 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)); // 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.

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 = false

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

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

Parâmetros posicionais opcionais
void saudar(String nome,
    [String titulo = "Sr."]) {
  print("Olá, $titulo $nome");
}

saudar("Ana");          // Sr. Ana
saudar("Ana", "Dra.");  // Dra. Ana

Parâ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


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

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

O 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


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

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

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

Um 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); // 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.

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 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).

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


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.

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


10 cards
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ório

O 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


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

O 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


10 cards
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 operator

Exceçõ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.