Cheatsheet Dart
Linguagem da Google, base do Flutter, para apps mobile, web e servidor
Dart
Básico e Variáveis
Función main
void main() {
print("¡Hola, Dart!");
}
// Con argumentos (CLI)
void main(List<String> args) {
print(args);
}main() es el punto de entrada de todo programa Dart. En scripts CLI puede recibir args como List<String>.
Interpolación de strings
String name = "Ana";
int age = 30;
print("Hola, $name");
print("Total: ${price * 2}");
print("Mayúsculas: ${name.toUpperCase()}");Usa $variable para valores simples y ${expresion} para expresiones o llamadas a método dentro de la string.
Operadores de comparación
5 == 5; // true igual 5 != 3; // true distinto 5 > 3; // true mayor 5 <= 5; // true menor o igual // idéntico (mismo valor y tipo) 1 == 1.0; // true (== compara valor)
== compara valores. Los operadores >, <, >= y <= comparan orden. != es la negación de igualdad.
Variables
var name = "Ana"; // tipo inferido (String) String city = "Porto"; int age = 30; double price = 9.99; bool active = true;
Usa var para que Dart infiera el tipo, o declara el tipo explícitamente. Una variable tipada no acepta valores de otro tipo.
Strings multilínea y raw
var text = """ Primera línea Segunda línea """; var raw = r"Sin \n escape aquí"; var path = r"C:\carpeta\archivo";
Tres comillas """ crean strings multilínea. El prefijo r crea una string raw, ignorando escapes como \n.
Operadores lógicos
true && false; // false (Y)
true || false; // true (O)
!true; // false (negación)
// cortocircuito
if (a != null && a.length > 0) {
// solo evalúa a.length si a no es null
}&& (Y) y || (O) son de cortocircuito: el segundo operando solo se evalúa si es necesario. ! niega un bool.
final y const
final name = "Ana"; // una asignación (runtime) const pi = 3.14; // constante de compilación final list = [1, 2]; list.add(3); // OK: el contenido cambia // list = []; // ERROR: reasignación
final permite una única asignación en runtime. const es inmutable desde la compilación y exige un valor conocido estáticamente.
Comentarios
// comentario de una línea
/* comentario
de varias líneas */
/// Documentación (genera docs con dartdoc)
/// [name] es el parámetro.
void func() {}Usa // para una línea, /* */ para un bloque y /// para documentación. Los comentarios doc generan API docs con la herramienta dartdoc.
Cascade (..)
var list = []
..add(1)
..add(2)
..add(3)
..sort();
var sb = StringBuffer()
..write("Hola ")
..write("Dart");El operador .. encadena varias operaciones sobre el mismo objeto sin repetir su nombre. Ideal para configurar objetos de forma compacta.
dynamic y Object
dynamic x = "texto"; x = 42; // OK: cualquier tipo x.metodoInexistente(); // compila, falla en runtime Object o = 10; // o.toUpperCase(); // ERROR: Object no lo tiene
dynamic desactiva la verificación de tipos (riesgo en runtime). Object es la raíz de todos los tipos, pero mantiene la seguridad estática.
Operadores aritméticos
int a = 10, b = 3; a + b; // 13 suma a - b; // 7 resta a * b; // 30 multiplicación a / b; // 3.33 división (double) a ~/ b; // 3 división entera a % b; // 1 resto
La división / devuelve siempre double. Usa ~/ para el cociente entero y % para el resto de la división.
Incremento y asignación
int x = 5; x++; // 6 (post-incremento) ++x; // 7 (pre-incremento) x += 3; // x = x + 3 x *= 2; // x = x * 2 x ??= 0; // asigna solo si es null
Los operadores compuestos +=, -=, *= combinan operación y asignación. ??= asigna solo si la variable es null.
Tipos de Dados
int y double
int x = 10; // entero double y = 3.14; // decimal num z = 5; // int o double num w = 2.5; // también válido int hex = 0xFF; // hexadecimal int bin = 0b1010; // binario (10)
int es entero y double decimal. num es la superclase de ambos. Soporta notación hexadecimal 0x y binaria 0b.
String: split y 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() devuelve una List<String>. replaceAll() reemplaza todas las ocurrencias y substring() extrae parte de la 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 empty = [];
La List es ordenada y dinámica. El tipo genérico List<int> restringe los elementos. first y last acceden a los extremos.
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 decimales)
round(), floor() y ceil() redondean de formas distintas. toStringAsFixed() formatea los decimales como string.
Conversión 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? error = int.tryParse("abc"); // nullint.parse() y double.parse() convierten strings en números y lanzan una excepción si son inválidas. tryParse() devuelve null en vez de fallar.
Map (mapa)
Map<String, int> ages = {
"Ana": 30,
"Ray": 25,
};
ages["Ana"]; // 30
ages["Ze"] = 40; // añade
ages.keys; // (Ana, Ray, Ze)
ages.values; // (30, 25, 40)El Map guarda pares clave-valor. Acceder a una clave inexistente devuelve null. keys y values devuelven iterables.
Propiedades numéricas
int n = 10; n.isEven; // true (par) n.isOdd; // false (impar) n.isNegative; // false double d = 3.14; d.isFinite; // true d.isNaN; // false
Las propiedades isEven/isOdd verifican paridad. isFinite y isNaN son útiles para validar resultados de cálculos.
Booleanos
bool active = true;
bool empty = list.isEmpty;
bool older = age >= 18;
// Dart exige un bool real (sin truthy/falsy)
if (active) { }
// if (1) { } // ERROR: int no es boolDart es estricto: las condiciones exigen un bool real, al contrario que JavaScript. No hay valores "truthy" — if (1) es error de compilación.
Set (conjunto)
Set<int> unique = {1, 2, 2, 3};
// {1, 2, 3} — duplicados eliminados
unique.add(3); // ignorado
unique.contains(1); // true
unique.length; // 3El Set guarda valores únicos sin orden garantizado. Añadir un duplicado no tiene efecto. Útil para eliminar repeticiones de una lista.
Métodos de String
String s = " Hola Mundo ";
s.trim(); // "Hola Mundo"
s.toLowerCase(); // minúsculas
s.toUpperCase(); // mayúsculas
s.length; // longitud
s.contains("Mundo"); // true
s.startsWith("Hola"); // false (espacios)Las String son inmutables: cada método devuelve una nueva string. trim() quita espacios en los extremos y contains() verifica substrings.
Runes y caracteres
String s = "Hola";
s.codeUnits; // códigos UTF-16
s.runes; // code points Unicode
var emoji = "\u{1F600}"; // 😀
print(emoji);runes da acceso a los code points Unicode (importante para emojis). \u{...} inserta un carácter Unicode por su código.
Verificación de tipo (is / as)
Object x = "texto";
if (x is String) {
print(x.length); // x se promueve a String
}
num n = 5;
int i = n as int; // cast explícitois verifica el tipo y promueve la variable dentro del bloque. as hace un cast explícito y lanza una excepción si el tipo no coincide.
Controlo de Fluxo
if / else
if (age >= 18) {
print("Adulto");
} else if (age > 12) {
print("Joven");
} else {
print("Niño");
}La estructura if/else if/else evalúa condiciones en orden y ejecuta el primer bloque verdadero. Los paréntesis son obligatorios.
for clásico
for (int i = 0; i < 10; i++) {
print(i);
}
for (int i = 10; i > 0; i--) {
print(i); // cuenta regresiva
}El ciclo for tiene inicialización, condición e incremento. Es ideal cuando necesitas el índice o controlar el contador manualmente.
break y continue
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // salta el 3
if (i == 7) break; // para en el 7
print(i);
}
// imprime 0,1,2,4,5,6continue salta a la siguiente iteración y break termina el ciclo inmediatamente. Ambos funcionan en for, while y switch.
Operador ternario
int age = 20;
String r = age >= 18 ? "Adulto" : "Menor";
// Encadenado (usar con moderación)
String level = grade >= 18 ? "A"
: grade >= 14 ? "B"
: "C";El ternario condición ? valorSiTrue : valorSiFalse es un if-else compacto en una expresión. Evita encadenar muchos para mantener la legibilidad.
for-in
var fruits = ["manzana", "pera", "uva"];
for (var fruit in fruits) {
print(fruit);
}
for (var key in map.keys) {
print("$key: ${map[key]}");
}El for-in recorre cada elemento de una colección sin índice. Es la forma más legible de iterar sobre una List, Set o Map.keys.
Labels en ciclos
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break outer; // sale del ciclo externo
}
}
}Un label (un nombre seguido de :) permite que break o continue actúen sobre un ciclo externo en loops anidados.
switch / case
switch (day) {
case 1:
print("Lunes");
break;
case 2:
print("Martes");
break;
default:
print("Otro");
}El switch compara un valor con varios case. Cada caso necesita un break (o return) para no continuar al siguiente.
while
int x = 5;
while (x > 0) {
print(x);
x--;
}
// x termina en 0El while repite mientras la condición sea verdadera. Verifica la condición antes de cada iteración, pudiendo no ejecutar nunca el bloque.
Pattern matching (Dart 3)
var (year, month, day) = (2024, 7, 24);
print("$day/$month/$year");
// Desestructurar lista
var [first, second, ...rest] = [1, 2, 3, 4];
// Guard en switch
var r = switch (value) {
int n when n > 0 => "positivo",
_ => "otro",
};Dart 3 introduce patterns: desestructuración de records y listas, y when para guardas condicionales en el switch.
Switch expression (Dart 3)
String name = switch (day) {
1 => "Lunes",
2 => "Martes",
3 => "Miércoles",
_ => "Otro",
};En Dart 3 el switch puede ser una expresión que devuelve un valor. _ es el caso por defecto (wildcard) y reemplaza a default.
do-while
int x = 0;
do {
print(x);
x++;
} while (x < 5);
// se ejecuta al menos una vezEl do-while ejecuta el bloque antes de verificar la condición, garantizando al menos una ejecución. Útil para menús y validaciones.
if-case (Dart 3)
var json = {"type": "circle", "radius": 5};
if (json case {"type": "circle", "radius": var r}) {
print("Círculo con radio $r");
}
// Patrones en declaraciones
var [a, b] = [1, 2];El if-case combina una condición con pattern matching, extrayendo valores de mapas y objetos de forma declarativa y segura.
Funções
Función simple
int add(int a, int b) {
return a + b;
}
void greeting(String name) {
print("Hola, $name");
}
print(add(2, 3)); // 5Declara el tipo de retorno antes del nombre. void indica que no devuelve valor. Los parámetros tipados aumentan la seguridad del código.
Funciones anónimas (lambda)
var list = [1, 2, 3];
list.forEach((n) => print(n));
var double = (int x) => x * 2;
print(double(5)); // 10
var sum = (int a, int b) {
return a + b;
};Las funciones sin nombre pueden asignarse a variables o pasarse como argumentos. Son la base de los callbacks y de métodos como map() y where().
Parámetro required
class User {
final String name;
final String email;
User({required this.name,
required this.email});
}
var u = User(name: "Ana", email: "a@b.com");required obliga a pasar el argumento nombrado, evitando null inesperados. Esencial en constructores y configuraciones.
Función arrow (=>)
int double(int x) => x * 2;
bool isEven(int n) => n % 2 == 0;
// Equivalente a:
int triple(int x) {
return x * 3;
}La sintaxis => expresion es un atajo para funciones de una sola expresión que devuelven ese valor. Reemplaza a { return ...; }.
Función como parámetro
int apply(int x, int Function(int) f) {
return f(x);
}
print(apply(5, (n) => n * 2)); // 10
print(apply(5, (n) => n + 1)); // 6Las funciones son objetos de primera clase en Dart. El tipo int Function(int) describe una función que recibe y devuelve un int.
Valores por omisión
void connect({
String host = "localhost",
int port = 3306,
bool ssl = false,
}) {
print("$host:$port ssl=$ssl");
}
connect(port: 5432);Los valores por omisión se aplican cuando el argumento no se proporciona. En parámetros nombrados deben ser constantes (const).
Parámetros nombrados
void call({required String number,
bool speaker = false}) {
print("$number speaker=$speaker");
}
call(number: "123", speaker: true);
call(number: "456"); // speaker = falseLos parámetros entre { } se pasan por el nombre, en cualquier orden. required los hace obligatorios y puede haber valores por omisión.
Función como retorno (closure)
Function multiplier(int factor) {
return (int x) => x * factor;
}
var doubler = multiplier(2);
var tripler = multiplier(3);
print(doubler(5)); // 10
print(tripler(5)); // 15Una función puede devolver otra función, creando un closure que captura variables del ámbito externo (aquí el factor).
Ámbito de variables
String global = "fuera";
void test() {
String local = "dentro";
print(global); // accesible
print(local);
}
// print(local); // ERROR: fuera de ámbitoLas variables declaradas dentro de una función son locales e inaccesibles fuera de ella. Dart tiene ámbito léxico: los bloques { } crean nuevos ámbitos.
Parámetros posicionales opcionales
void greet(String name,
[String title = "Sr."]) {
print("Hola, $title $name");
}
greet("Ana"); // Sr. Ana
greet("Ana", "Dra."); // Dra. AnaLos parámetros entre [ ] son posicionales y opcionales. Deben ir después de los obligatorios y pueden tener un valor por omisión.
Funciones genéricas
T first<T>(List<T> list) {
return list.first;
}
print(first<int>([1, 2, 3])); // 1
print(first<String>(["a", "b"])); // "a"<T> hace la función genérica, funcionando con cualquier tipo. El tipo puede inferirse o especificarse explícitamente en la llamada.
Tear-off y referencia de método
var list = [1, 2, 3]; // Tear-off: referencia al método list.forEach(print); // Equivalente a: list.forEach((n) => print(n)); var double = [1, 2].map((n) => n * 2);
Un tear-off es pasar un método directamente (ej.: print) en vez de envolverlo en una lambda. Más limpio cuando la firma coincide.
Coleções
Operaciones de List
var list = [1, 2, 3]; list.add(4); // añade al final list.remove(1); // elimina el valor 1 list.removeAt(0); // elimina por índice list.insert(0, 9); // inserta en la posición list.contains(2); // true list.indexOf(3); // posición
La List es dinámica: add() inserta al final, remove() elimina por valor, insert() coloca en una posición específica.
sort() y reversed
var list = [3, 1, 4, 1, 5]; list.sort(); // [1, 1, 3, 4, 5] list.sort((a, b) => b.compareTo(a)); // desc var names = ["ana", "rui"]; names.reversed.toList(); // ["rui", "ana"]
sort() ordena la lista en el lugar (muta la original). Pasa un comparador para orden personalizado. reversed devuelve el orden inverso.
forEach e indexed
var fruits = ["manzana", "pera"];
fruits.forEach((f) => print(f));
// Con índice (Dart 3)
for (var (i, f) in fruits.indexed) {
print("$i: $f");
}forEach() ejecuta una función para cada elemento. .indexed proporciona pares (índice, valor) para iterar con la posición.
map() y where()
var list = [1, 2, 3, 4, 5]; var double = list.map((e) => e * 2).toList(); // [2, 4, 6, 8, 10] var evens = list.where((e) => e % 2 == 0).toList(); // [2, 4]
map() transforma cada elemento y where() filtra. Ambos devuelven un Iterable; usa toList() para materializarlo.
Spread operator (...)
var a = [1, 2];
var b = [0, ...a, 3]; // [0, 1, 2, 3]
List<int>? nullable;
var c = [...?nullable, 1]; // spread null-safe
var map = {...otherMap, "x": 1};... expande los elementos de una colección dentro de otra. ...? evita un error si la colección es null.
Map: transformaciones
var map = {"a": 1, "b": 2};
var doubled = map.map((k, v) => MapEntry(k, v * 2));
// {"a": 2, "b": 4}
map.containsKey("a"); // true
map.remove("b"); // elimina la claveEl map() de un Map devuelve MapEntry para transformar claves y valores. containsKey() verifica la existencia de una clave.
any() y every()
var list = [1, 2, 3, 4]; list.any((e) => e > 3); // true (alguno) list.every((e) => e > 0); // true (todos) list.contains(2); // true list.isEmpty; // false
any() verifica si al menos un elemento cumple la condición y every() si todos la cumplen.
Collection-if y for
bool active = true; var items = [ "base", if (active) "extra", for (var i in [1, 2]) "item$i", ]; // ["base", "extra", "item1", "item2"]
Dentro de literales de colección puedes usar if y for para construir listas dinámicamente, sin lógica externa.
Set: operaciones 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]); // trueEl Set ofrece operaciones matemáticas: union() (unión), intersection() (intersección) y difference() (diferencia).
fold() y reduce()
var list = [1, 2, 3, 4]; int sum = list.fold(0, (acc, e) => acc + e); // 10 int product = list.reduce((acc, e) => acc * e); // 24
fold() acumula un valor a partir de un valor inicial. reduce() hace lo mismo pero usa el primer elemento como punto de partida.
List.generate()
var squares = List.generate(5, (i) => i * i); // [0, 1, 4, 9, 16] var evens = List.generate(10, (i) => i * 2); // [0, 2, 4, ..., 18]
List.generate() crea una lista a partir de una función que recibe el índice. Ideal para generar secuencias o datos de prueba.
Colecciones inmutables
var fixed = const [1, 2, 3]; // fixed.add(4); // ERROR: inmutable var immutable = List.unmodifiable([1, 2, 3]); // immutable[0] = 9; // ERROR en runtime var readOnly = [1, 2, 3] as List<int>;
Una lista const es inmutable en compilación. List.unmodifiable() crea una vista de solo lectura que lanza un error al intentar modificarla.
Classes e OOP
Clase básica
class Person {
String name;
int age;
Person(this.name, this.age);
}
var p = Person("Ana", 30);
print(p.name); // AnaEl constructor Person(this.name, this.age) asigna automáticamente los argumentos a los campos. Es la forma idiomática y compacta de Dart.
Clase abstracta
abstract class Shape {
double area();
}
class Circle extends Shape {
final double radius;
Circle(this.radius);
@override
double area() => 3.14 * radius * radius;
}Una clase abstract no puede instanciarse y define un contrato. Las subclases están obligadas a implementar los métodos abstractos.
Enum avanzado
enum Planet {
earth(9.8),
mars(3.7),
moon(1.6);
final double gravity;
const Planet(this.gravity);
}
print(Planet.earth.gravity); // 9.8Los enums en Dart pueden tener campos, constructores y métodos. Cada valor pasa argumentos al constructor const, permitiendo datos asociados.
Miembros estáticos
class MyMath {
static const double pi = 3.14159;
static double circleArea(double r) {
return pi * r * r;
}
}
print(MyMath.pi);
print(MyMath.circleArea(2));Los miembros static pertenecen a la clase y no a la instancia. Se acceden por el nombre de la clase, sin crear un objeto. Útil para utilidades y constantes.
Constructor nombrado
class Point {
final int x, y;
const Point(this.x, this.y);
Point.origin() : this(0, 0);
Point.axisX(this.x) : y = 0;
}
var p = Point.origin();Los constructores nombrados como Point.origin() ofrecen formas alternativas de crear objetos. El : this(...) delega en otro constructor.
Interfaz (implements)
class Point {
int x = 0, y = 0;
}
class Point3D implements Point {
int x = 0, y = 0, z = 0;
}
void printPoint(Point p) => print(p.x);En Dart cualquier clase puede ser una interfaz con implements. La clase debe reimplementar todos los miembros de la interfaz.
Extension methods
extension StringExtra on String {
String get reversed =>
split("").reversed.join();
bool get isNumber =>
int.tryParse(this) != null;
}
print("abc".reversed); // "cba"
print("123".isNumber); // trueUna extension añade métodos a tipos existentes sin modificarlos. Se acceden como si fueran métodos nativos del 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(); // misma instanciaUn constructor factory controla la creación del objeto, pudiendo devolver instancias existentes (singleton) o de subclases.
Getters y setters
class Rectangle {
double width = 0, height = 0;
double get area => width * height;
set area(double v) {
width = height = v;
}
}
var r = Rectangle();
r.area = 5; // setter
print(r.area); // getterget crea una propiedad computada de solo lectura y set permite la asignación con lógica. Se acceden como campos normales, sin paréntesis.
Mixin (with)
mixin Swimmer {
void swim() => print("nadando");
}
mixin Flyer {
void fly() => print("volando");
}
class Duck extends Animal with Swimmer, Flyer {}
Duck().swim();
Duck().fly();Un mixin añade comportamiento a varias clases sin herencia clásica. Se aplica con with y puede combinar varios mixins.
Clases genéricas
class Box<T> {
T value;
Box(this.value);
}
var c = Box<int>(42);
var s = Box<String>("hola");
print(c.value); // 42<T> hace la clase reutilizable para cualquier tipo. El tipo se fija en la instanciación, garantizando seguridad de tipos.
Clases inmutables
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
const a = Point(1, 2);
const b = Point(1, 2);
print(identical(a, b)); // true (canonical)Los campos final + un constructor const crean objetos inmutables. Las instancias const idénticas son canónicas (compartidas en memoria).
Herencia (extends)
class Animal {
void speak() => print("...");
}
class Dog extends Animal {
@override
void speak() => print("Guau guau");
}
Dog().speak(); // Guau guauextends hereda campos y métodos de la superclase. @override indica que estás reescribiendo un método heredado.
Enum simple
enum Status { active, inactive, pending }
Status s = Status.active;
print(s.name); // "active"
print(s.index); // 0
if (s == Status.active) {
print("active!");
}El enum define un conjunto fijo de valores. .name devuelve el nombre como string y .index la posición (empieza en 0).
super parameters (Dart 2.17+)
class Animal {
final String name;
Animal(this.name);
}
class Dog extends Animal {
final String breed;
Dog(super.name, this.breed);
}
var c = Dog("Rex", "Labrador");super.name en el constructor reenvía automáticamente el argumento a la superclase, evitando escribir : super(name).
Operador == y hashCode
class Point {
final int x, y;
const Point(this.x, this.y);
@override
bool operator ==(Object o) =>
o is Point && o.x == x && o.y == y;
@override
int get hashCode => Object.hash(x, y);
}Para comparar objetos por valor, sobrescribe operator == y hashCode. Object.hash() genera un hash consistente de los campos.
Assíncrono
async / await
Future<String> fetch() async {
var r = await http.get(url);
return r.body;
}
void main() async {
var data = await fetch();
print(data);
}async marca una función asíncrona y await pausa la ejecución hasta que el Future complete, manteniendo el código legible.
Future.wait (paralelo)
var results = await Future.wait([ fetchA(), fetchB(), fetchC(), ]); print(results); // [a, b, c]
El Future.wait() ejecuta varios Future en paralelo y espera todos. Más rápido que el await secuencial cuando son independientes.
async* y yield*
Stream<int> upTo(int n) async* {
for (var i = 0; i <= n; i++) {
yield i;
}
}
Stream<int> doubled() async* {
yield* upTo(3).map((e) => e * 2);
}El async* devuelve un Stream y el yield emite valores. El yield* reemite todos los valores de otra stream.
Future
Future<String> task() {
return Future.delayed(
Duration(seconds: 2),
() => "ready",
);
}
task().then((v) => print(v));Un Future<T> representa un valor disponible en el futuro. El .then() registra un callback ejecutado cuando el resultado esté listo.
StreamController
final controller = StreamController<int>(); controller.stream.listen((n) => print(n)); controller.add(1); controller.add(2); await controller.close();
El StreamController permite emitir valores manualmente a una stream vía .add(). El .listen() se suscribe a los 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]El sync* crea un generador síncrono que devuelve un Iterable. Los valores se producen de forma perezosa (lazy) al iterar.
try / catch async
try {
var data = await fetch();
print(data);
} catch (e) {
print("Error: $e");
} finally {
print("finalizado");
}Los errores en código await se capturan con un try/catch normal. El finally se ejecuta siempre, con o sin error.
listen y cancelación
var sub = stream.listen(
(data) => print(data),
onError: (e) => print("Error: $e"),
onDone: () => print("fin"),
);
// Más tarde:
await sub.cancel();El listen() se suscribe a una stream con callbacks de datos, error y fin. Guarda la StreamSubscription para cancelar con .cancel().
Timeout
try {
var r = await fetch()
.timeout(Duration(seconds: 5));
} on TimeoutException {
print("Tardó demasiado");
}El timeout() lanza TimeoutException si el Future no completa a tiempo. Esencial para peticiones de red robustas.
Stream
Stream<int> count() async* {
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
await for (var n in count()) {
print(n);
}Un Stream emite varios valores a lo largo del tiempo. El async* con yield produce los valores y el await for los consume.
then / catchError
fetch()
.then((v) => print(v))
.catchError((e) {
print("Falló: $e");
return "fallback";
})
.whenComplete(() => print("fin"));El then() encadena el éxito, catchError() trata errores (pudiendo devolver un fallback) y whenComplete() se ejecuta siempre al final.
Isolates (paralelismo)
import 'dart:isolate';
int heavyCalc(int n) {
var sum = 0;
for (var i = 0; i < n; i++) sum += i;
return sum;
}
// Corre en otro isolate (thread)
var result = await Isolate.run(
() => heavyCalc(1000000),
);Dart es single-thread por isolate. Isolate.run() ejecuta trabajo pesado en otro isolate, evitando bloquear la UI. Cada isolate tiene su propia memoria.
Null Safety
Tipos nullable (?)
String? name; // acepta null name = null; // OK String s = "x"; // s = null; // ERROR: no-nullable int? number; // nullable int age = 0; // nunca null
Por defecto los tipos no aceptan null. El sufijo ? hace el tipo nullable, permitiendo asignar null con seguridad.
required
class User {
final String email;
final String? phone;
User({required this.email, this.phone});
}
var u = User(email: "a@b.com");
// User(); // ERROR: email obligatorioEl required hace obligatorio un parámetro nombrado, evitando null inesperados. Los parámetros sin él deben ser nullable o tener default.
Null-aware en cascade (..?)
StringBuffer? sb = getBuffer();
sb?..write("Hola")
..write(" Dart");
// solo se ejecuta si sb != null
list?..add(1)..sort();El ?.. combina null-aware con cascade: la cadena solo se ejecuta si el objeto no es null, evitando excepciones en configuraciones opcionales.
Operador ?? (fallback)
String? input; var name = input ?? "Anónimo"; // "Anónimo" si input es null int? attempts; attempts ??= 0; // asigna solo si es null
El ?? devuelve el operando izquierdo si no es null, si no el derecho. El ??= asigna un valor solo si la variable es null.
late (inicialización tardía)
class Service {
late final ApiClient api;
Service() {
api = ApiClient(); // después del constructor
}
}
late String config = loadConfig();El late permite declarar una variable non-null inicializada más tarde. Dart solo verifica la asignación en el primer acceso a la variable.
Retorno nullable
int? findIndex(List l, Object v) {
int i = l.indexOf(v);
return i >= 0 ? i : null;
}
var idx = findIndex([1, 2], 2);
if (idx != null) print("posición $idx");Las funciones que pueden fallar devuelven un tipo nullable (int?). El llamador verifica con != null antes de usar el resultado.
Operador ?. (acceso seguro)
String? name; int? len = name?.length; // null, sin error // En cadena var city = user?.address?.city; // Con método list?.add(1); // solo llama si list != null
El ?. interrumpe la cadena y devuelve null en vez de lanzar una excepción si el objeto es null. Ideal para accesos encadenados.
Promoción de tipo (flow analysis)
String? name = getName();
if (name != null) {
// aquí name se promueve a String
print(name.length); // sin !
}
// También con is
Object x = "texto";
if (x is String) print(x.length);Tras verificar != null o is, Dart promueve la variable al tipo non-null dentro del bloque, prescindiendo del operador !.
Operador ! (forzar non-null)
String? name = "Ana"; int len = name!.length; // afirma non-null // CUIDADO: lanza error si es null String? nullValue; // nullValue!.length; // Null check operator error
El ! afirma al compilador que el valor no es null. Si está mal, lanza error en runtime. Úsalo solo cuando estés seguro.
Colecciones nullable
List<int>? list;
list?.add(1); // seguro
var l = list ?? []; // fallback
Map<String, int?> map = {
"a": 1,
"b": null, // valor nullable
};Una colección puede ser nullable (List?) o tener elementos nullable (List<int?>). Usa ?. y ?? según el caso.
Genéricos, Records e Patterns
Genéricos en clases
class Stack<T> {
final List<T> _items = [];
void push(T item) => _items.add(item);
T pop() => _items.removeLast();
bool get empty => _items.isEmpty;
}
var p = Stack<int>();
p.push(1);Los genéricos (<T>) crean clases reutilizables con seguridad de tipos. El tipo se fija en la instanciación, evitando casts en runtime.
Desestructuración (destructuring)
var list = [1, 2, 3, 4];
var [a, b, ...rest] = list;
// a=1, b=2, rest=[3,4]
var map = {"x": 10, "y": 20};
var {"x": x, "y": y} = map;Dart 3 permite desestructurar listas y mapas en variables individuales. El ...rest captura los elementos restantes en una nueva lista.
Parámetros de tipo múltiples
Map<K, V> invert<K, V>(Map<V, K> map) {
return map.map((k, v) => MapEntry(v, k));
}
var original = {1: "uno", 2: "dos"};
var inverted = invert(original);
// {"uno": 1, "dos": 2}Las funciones pueden tener varios parámetros de tipo (<K, V>). El compilador los infiere a partir de los argumentos pasados.
Bibliotecas e imports
import 'dart:math'; import 'package:http/http.dart' as http; import 'utils.dart' show format; import 'other.dart' hide internal; export 'model.dart';
El import trae bibliotecas; as crea un prefijo, show/hide filtran símbolos. export reexporta desde otra biblioteca.
Restricciones genéricas (bounds)
T greater<T extends Comparable<T>>(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
print(greater(5, 10)); // 10
print(greater("a", "z")); // "z"T extends Comparable<T> restringe el genérico a tipos comparables. El compilador garantiza que compareTo() existe.
Switch con patterns
String describe(Object value) => switch (value) {
int n => "entero $n",
String s => "texto $s",
[1, 2] => "lista [1,2]",
{ "name": var n } => "mapa name=$n",
_ => "otro",
};El switch con patterns hace matching por tipo, valor y estructura. Extrae valores directamente a variables (ej.: int n, var n).
Object vs dynamic vs void
Object a = 1; // cualquier tipo, seguro dynamic b = 1; // sin verificación (riesgo) void c = func(); // no usar el valor // a.methodX(); // ERROR en compilación b.methodX(); // compila, falla en runtime
Object es la raíz tipada (segura), dynamic desactiva verificaciones y void indica que el valor no debe usarse.
Partes y extensión de bibliotecas
// lib.dart
library my_lib;
part 'part_a.dart';
part 'part_b.dart';
// part_a.dart
part of 'lib.dart';
void funcA() {}part y part of dividen una biblioteca en varios archivos que comparten el mismo ámbito. Útil para organizar código extenso.
Records (Dart 3)
(String, int) person = ("Ana", 30);
print(person.$1); // Ana
print(person.$2); // 30
// Con nombres
({String name, int age}) p =
(name: "Ana", age: 30);
print(p.name);Los records agrupan varios valores sin crear una clase. Pueden ser posicionales ($1, $2) o nombrados, y son inmutables.
Guardas (when)
String classify(int n) => switch (n) {
< 0 => "negativo",
0 => "zero",
> 0 && < 10 => "pequeño",
_ => "grande",
};
// Guarda con when
var r = switch (value) {
int x when x.isEven => "par",
_ => "impar",
};Los relational patterns (<, >) y la guarda when permiten condiciones refinadas dentro de los casos del switch.
Constantes en tiempo de compilación
const list = [1, 2, 3];
const map = {"a": 1};
const point = Point(1, 2); // constructor const
// Deep const: todo es inmutable
const nested = [1, [2, 3]];
// nested[1].add(4); // ERROREl const crea valores inmutables en compilación, incluyendo colecciones anidadas (deep const). Reduce asignaciones y mejora el rendimiento.
Records como retorno
(double, double) divide(int a, int b) {
return (a / b, a % b.toDouble());
}
var (quotient, remainder) = divide(10, 3);
print("$quotient resto $remainder");Las funciones pueden devolver varios valores vía records, sin crear una clase. La desestructuración var (a, b) = ... extrae los valores.
typedef (alias de tipos)
typedef Callback = void Function(String);
typedef Comparator<T> = int Function(T, T);
void register(Callback cb) {
cb("ok");
}
register((msg) => print(msg));El typedef crea un nombre alternativo para un tipo, útil para firmas de funciones complejas y genéricos reutilizables.
assert
void setAge(int age) {
assert(age >= 0, "Edad inválida");
// solo activo en modo debug
}
assert(list.isNotEmpty);
assert(x > 0, "x debe ser positivo");El assert verifica una condición en modo debug y lanza AssertionError si es falsa. Se ignora en producción (no afecta el rendimiento).
Exceções e Erros
try / catch
try {
int r = 10 ~/ 0;
} catch (e) {
print("Error: $e");
}
// Capturar tipo específico
try {
risky();
} on FormatException {
print("formato inválido");
}El try/catch captura excepciones en runtime. El on filtra por tipo de excepción, permitiendo un tratamiento específico para cada caso.
Excepción personalizada
class InsufficientBalance implements Exception {
final double amount;
InsufficientBalance(this.amount);
@override
String toString() =>
"Saldo insuficiente: $amount";
}
throw InsufficientBalance(100.0);Crea clases que implementan Exception para errores específicos del dominio. Sobrescribe toString() para mensajes claros.
tryParse (evitar excepciones)
// En vez de try/catch:
int? n = int.tryParse("abc"); // null
if (n != null) {
print("número: $n");
}
double? d = double.tryParse("3.14");Los métodos tryParse() devuelven null en vez de lanzar una excepción. Preferible para validación de input, más limpio y eficiente.
finally
try {
var data = await readFile();
process(data);
} catch (e) {
print("Falló: $e");
} finally {
closeResources(); // se ejecuta siempre
}El bloque finally se ejecuta siempre, haya o no excepción. Ideal para liberar recursos como archivos, conexiones o subscriptions.
Capturar stack trace
try {
operation();
} catch (e, stack) {
print("Error: $e");
print("Stack: $stack");
}El segundo parámetro del catch (stack) es la StackTrace, útil para depurar dónde ocurrió la excepción.
Errores vs Excepciones
// Error: bug de programación (no recuperar)
// StateError, ArgumentError, RangeError
// Exception: condición esperada (recuperable)
// FormatException, IOException, HttpException
throw StateError("estado inválido");
throw FormatException("input malo");El Error indica bugs (no deben capturarse normalmente). La Exception representa condiciones esperadas y recuperables del programa.
throw (lanzar excepción)
void setAge(int age) {
if (age < 0) {
throw ArgumentError("Edad inválida: $age");
}
}
void save(Object? v) {
if (v == null) throw Exception("vacío");
}El throw lanza una excepción. Puedes usar tipos nativos como ArgumentError o Exception, o crear los tuyos propios.
Excepciones comunes
int.parse("abc"); // FormatException
[1,2][5]; // RangeError
null!.length; // TypeError (null check)
10 ~/ 0; // UnsupportedError
{}["x"]!; // null check operatorExcepciones frecuentes: FormatException (parse inválido), RangeError (índice fuera), TypeError (tipo/null erróneo).
rethrow
try {
operation();
} catch (e) {
print("registrar: $e");
rethrow; // relanza la misma excepción
}El rethrow relanza la excepción actual preservando la stack trace original. Útil para registrar el error antes de propagarlo.
runZonedGuarded
import 'dart:async';
runZonedGuarded(() {
// código que puede lanzar errores
asyncOperation();
}, (error, stack) {
print("Error global: $error");
});El runZonedGuarded() captura errores no tratados dentro de una zona, incluidos los asíncronos. Útil como handler global de errores.