DevTools

Cheatsheet Flutter

Framework para desenvolvimento mobile

Volver a los lenguajes
Flutter
132 tarjetas encontradas
Categorías:
Versiones:

Sintaxe Básica (Dart)


14 cards
Variables
int edad = 30;
double precio = 9.99;
String nombre = "Ana";
bool activo = true;
var total = 100;      // tipo inferido (int)
dynamic x = "texto";  // acepta cualquier tipo

Usa var cuando quieras que Dart infiera el tipo. dynamic desactiva la verificación de tipos y debe evitarse en código de producción.

Interpolación de strings
String nombre = "Ana";
int edad = 30;

String s = "Hola $nombre";
String r = "Total: ${precio * 2}";
String m = "Nombre: ${nombre.toUpperCase()}";

Usa $variable para insertar valores simples y ${expressao} para expresiones o llamadas a métodos dentro de la string.

Operadores null-aware
String? nombre;
nombre ??= "Anónimo";   // asigna si es null
var x = nombre ?? "N/D"; // fallback

// Acceso seguro en cadena
int? len = nombre?.length;
var ciudad = usuario?.dirección?.ciudad;

??= asigna solo si la variable es null. ?. interrumpe la cadena y devuelve null en vez de lanzar una excepción.

late (inicialización tardía)
class Servico {
  late final ApiClient api;

  Servico() {
    api = ApiClient(); // inicializada después
  }
}

late String config = loadConfig();

late permite declarar una variable non-null que se inicializará más tarde. Dart solo verifica la asignación en el primer acceso.

final y const
final nombre = "Ana";   // asignada una vez (runtime)
const pi = 3.14;      // constante de compilación

final lista = [1, 2];
lista.add(3);         // OK: el contenido puede cambiar
// lista = [4];       // ERROR: no se puede reasignar

final permite una única asignación en runtime. const es inmutable desde la compilación y debe tener un valor conocido estáticamente.

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.split(" ");          // lista de palabras
s.replaceAll("o", "0"); // reemplaza

Las String son inmutables: cada método devuelve una nueva string. trim() quita los espacios y split() devuelve una List<String>.

Cascade (..)
var lista = []
  ..add(1)
  ..add(2)
  ..add(3)
  ..sort();

var paint = Paint()
  ..color = Colors.blue
  ..strokeWidth = 2.0;

El operador .. encadena varias operaciones sobre el mismo objeto sin repetir su nombre. Muy útil para configurar objetos de forma compacta.

Type aliases (typedef)
typedef Callback = void Function(String);
typedef Par = (int, int);

void registrar(Callback cb) {
  cb("ok");
}

registrar((msg) => print(msg));

typedef crea un nombre alternativo para un tipo, útil para firmas de funciones complejas. En Dart 3 también define records nombrados.

Tipos de datos
int n = 10;          // entero
double d = 3.14;     // decimal
num x = 5;           // int o double
String s = "texto";  // texto
bool b = true;        // booleano
List<int> l = [1, 2]; // lista
Map<String, int> m = {}; // mapa
Set<int> c = {1, 2};  // conjunto

Dart es fuertemente tipado. num es la superclase de int y double. Las colecciones usan genéricos como List<int>.

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 (módulo)
a++;     // incremento

La división / devuelve siempre un double. Usa ~/ para obtener el cociente entero y % para el resto de la división.

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"); // null (sin excepción)

int.parse() y double.parse() convierten strings en numeros y lanzan una excepción si son inválidas. tryParse() devuelve null en vez de fallar.

Null safety
String? nombre;          // acepta null
nombre ??= "Anónimo";    // asigna si es null
int len = nombre!.length; // fuerza non-null
String s = nombre ?? "patrón"; // valor alternativo

String? nulo = null;
nulo?.length;          // retorna null, no error

Por defecto los tipos no aceptan null. El ? hace el tipo nullable, ?? da un fallback y ?. evita excepciones en cadenas.

Comparación y lógicos
// Comparación
5 == 5;   // true
5 != 3;   // true
5 > 3;    // true
5 <= 5;   // true

// Lógicos
true && false;  // false (Y)
true || false;  // true  (O)
!true;          // false (negación)

Los operadores && (Y) y || (O) son de cortocircuito: el segundo operando solo se evalúa si es necesario. ! niega un bool.

Números (int y double)
double d = 9.876;
d.round();        // 10
d.floor();        // 9
d.ceil();         // 10
d.toStringAsFixed(2); // "9.88"
d.abs();          // valor absoluto

int n = -5;
n.abs();          // 5
n.isEven;         // false

Los métodos round(), floor() y ceil() redondean de formas distintas. toStringAsFixed() formatea los decimales.

Controlo de Fluxo


10 cards
if / else
if (edad >= 18) {
  print("adulto");
} else if (edad > 12) {
  print("joven");
} else {
  print("niño");
}

La estructura if/else if/else evalúa las 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,6

continue salta a la siguiente iteración y break termina el ciclo inmediatamente. Ambos funcionan en for, while y switch.

if como expresión
// En colecciones (collection-if)
var ítems = [
  "sempre",
  if (activo) "activo",
  "fin",
];

// Asignación con ternario
String r = activo ? "sim" : "no";

Dentro de listas puedes usar if para incluir elementos condicionalmente. Para expresiones simples prefiere el operador ternario ? :.

for-in
var frutas = ["manzana", "pera", "uva"];

for (var fruta in frutas) {
  print(fruta);
}

for (var i in [1, 2, 3]) {
  print(i * 2);
}

for-in recorre cada elemento de una colección sin necesitar índice. Es la forma más legible de iterar sobre una List, Set o Map.keys.

Operador ternario
int edad = 20;
String r = edad >= 18 ? "adulto" : "menor";

// Encadenado (evitar en exceso)
String nivel = nota >= 18 ? "A"
             : nota >= 14 ? "B"
             : "C";

El ternario condición ? valorSeTrue : valorSeFalse es un if-else compacto en una expresión. Evita encadenar muchos para mantener la legibilidad.

switch / case
switch (dia) {
  case 1:
    print("Segunda");
    break;
  case 2:
    print("Martes");
    break;
  default:
    print("Outro");
}

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 0

while repite mientras la condición sea verdadera. Verifica la condición antes de cada iteración, pudiendo no ejecutar nunca el bloque.

Switch expression (Dart 3)
String nombre = switch (dia) {
  1 => "Segunda",
  2 => "Martes",
  3 => "Quarta",
  _ => "Outro",
};

// Con patrones
var tipo = switch (valor) {
  int() => "entero",
  String() => "texto",
  _ => "desconocido",
};

En Dart 3 el switch puede ser una expresión que devuelve un valor. El _ es el caso por defecto y los patterns permiten un matching avanzado.

do-while
int x = 0;
do {
  print(x);
  x++;
} while (x < 5);
// se ejecuta al menos una vez

do-while ejecuta el bloque antes de verificar la condición, garantizando al menos una ejecución. Útil para menús y validaciones.

Funções


12 cards
Función simple
int sumar(int a, int b) {
  return a + b;
}

void saludo(String nombre) {
  print("Hola $nombre");
}

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

Declara el tipo de retorno antes del nombre. void indica que no devuelve valor. Los parámetros pueden tener tipos explícitos para mayor seguridad.

Valores por defecto
void conectar({
  String host = "localhost",
  int puerto = 3306,
  bool ssl = false,
}) {
  print("$host:$puerto ssl=$ssl");
}

conectar(puerto: 5432);

Los valores por defecto se aplican cuando el argumento no se proporciona. En parámetros nombrados, los opcionales son automáticamente nullable o con default.

Funciones genéricas
T primero<T>(List<T> lista) {
  return lista.first;
}

print(primero<int>([1, 2, 3]));    // 1
print(primero<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.

Arrow function (=>)
int doble(int x) => x * 2;

bool ePar(int n) => n % 2 == 0;

// Equivalente a:
int triple(int x) {
  return x * 3;
}

La sintaxis => expressao es un atajo para funciones de una sola expresión que devuelven ese valor. Sustituye a { return ...; }.

Funciones anónimas (lambda)
var lista = [1, 2, 3];
lista.forEach((n) => print(n));

var doble = (int x) => x * 2;
print(doble(5)); // 10

var suma = (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().

Función main
void main() {
  print("Inicio da app");
}

// Con argumentos (CLI)
void main(List<String> args) {
  print(args);
}

main() es el punto de entrada de cualquier programa Dart. En apps Flutter es donde se llama a runApp().

Parámetros nombrados
void hola({required String nombre, int edad = 0}) {
  print("$nombre tem $edad");
}

hola(nombre: "Ana", edad: 30);
hola(nombre: "Ray"); // edad = 0

Los parámetros entre { } se pasan por nombre, en cualquier orden. required los hace obligatorios y pueden tener valores por defecto.

Función 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

Las funciones son objetos de primera clase en Dart. El tipo int Function(int) describe una función que recibe y devuelve un int.

Parámetro required
class Usuario {
  final String nombre;
  final String email;

  Usuario({required this.nombre, required this.email});
}

var u = Usuario(nombre: "Ana", email: "a@b.com");

required obliga a pasar el argumento nombrado, evitando null inesperados. Es esencial en constructores de widgets y modelos.

Parámetros posicionales opcionales
void log(String msg, [int nivel = 0]) {
  print("[$nivel] $msg");
}

log("error", 2); // [2] error
log("info");    // [0] info

Los parámetros entre [ ] son posicionales y opcionales. Deben ir después de los obligatorios y pueden tener valor por defecto.

Función como retorno
Function multiplicador(int fator) {
  return (int x) => x * fator;
}

var dobrar = multiplicador(2);
var triplicar = multiplicador(3);
print(dobrar(5));    // 10
print(triplicar(5)); // 15

Una función puede devolver otra función, creando closures que capturan variables del ámbito externo (aquí el fator).

Ámbito de variables
String global = "fora";

void prueba() {
  String local = "dentro";
  print(global); // accesible
  print(local);
}
// print(local); // ERROR: fuera de ámbito

Las variables declaradas dentro de una función son locales e inaccesibles fuera de ella. Dart tiene ámbito léxico: los bloques { } crean nuevos ámbitos.

Classes e OOP


16 cards
Clase básica
class Persona {
  String nombre;
  int edad;

  Persona(this.nombre, this.edad);
}

var p = Persona("Ana", 30);
print(p.nombre); // Ana

El constructor Persona(this.nombre, this.edad) asigna automáticamente los argumentos a los campos. Es la forma idiomática y compacta de Dart.

Clase abstracta
abstract class Forma {
  double area();
}

class Círculo extends Forma {
  final double radio;
  Círculo(this.radio);

  @override
  double area() => 3.14 * radio * radio;
}

Una clase abstract no puede instanciarse y define un contrato. Las subclases están obligadas a implementar los métodos abstractos.

Enum avanzado (Dart 2.17+)
enum Planet {
  tierra(9.8),
  marte(3.7),
  luna(1.6);

  final double gravedad;
  const Planet(this.gravedad);
}

print(Planet.tierra.gravedad); // 9.8

Los enums en Dart pueden tener campos, constructores y métodos. Cada valor pasa argumentos al constructor const, permitiendo datos asociados.

super parameters (Dart 2.17+)
class Animal {
  final String nombre;
  Animal(this.nombre);
}

class Perro extends Animal {
  final String raca;
  Perro(super.nombre, this.raca);
}

var c = Perro("Rex", "Labrador");

super.nombre en el constructor reenvía automáticamente el argumento a la superclase, evitando escribir : super(nombre).

Constructor nombrado
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();

Los constructores nombrados como Ponto.origem() ofrecen formas alternativas de crear objetos. El : this(...) delega en otro constructor.

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

En Dart cualquier clase puede ser una interface con implements. La clase debe reimplementar todos los miembros de la 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

Una extension añade métodos a tipos existentes sin modificarlos. Se acceden como si fueran métodos nativos del tipo.

Miembros estáticos
class Matemática {
  static const double pi = 3.14159;

  static double areaCirculo(double r) {
    return pi * r * r;
  }
}

print(Matemática.pi);
print(Matemática.areaCirculo(2));

Los miembros static pertenecen a la clase y no a la instancia. Se acceden por el nombre de la clase, sin crear objeto. Útil para utilidades y constantes.

Getters y Setters
class Rectángulo {
  double ancho = 0, altura = 0;

  double get area => ancho * altura;

  set area(double v) {
    ancho = altura = v;
  }
}

var r = Rectángulo();
r.area = 5;       // llama al setter
print(r.area);    // llama al getter

get 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 Nadador {
  void nadar() => print("a nadar");
}

mixin Volador {
  void volar() => print("a volar");
}

class Pato extends Animal with Nadador, Volador {}

Pato().nadar();
Pato().volar();

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 Caja<T> {
  T valor;
  Caja(this.valor);
}

var c = Caja<int>(42);
var s = Caja<String>("hola");
print(c.valor); // 42

<T> hace la clase reutilizable para cualquier tipo. El tipo se fija en la instanciación, garantizando la seguridad de tipos.

Records (Dart 3)
(String, int) persona = ("Ana", 30);
print(persona.$1); // Ana
print(persona.$2); // 30

// Con nombres
({String nombre, int edad}) p = (nombre: "Ana", edad: 30);
print(p.nombre);

Los records agrupan varios valores sin crear una clase. Pueden ser posicionales ($1, $2) o nombrados.

Herencia (extends)
class Animal {
  void hablar() => print("...");
}

class Perro extends Animal {
  @override
  void hablar() => print("Au au");
}

Perro().hablar(); // Au au

extends hereda campos y métodos de la superclase. @override indica que estás reescribiendo un método heredado.

Enum simple
enum Estado { activo, inactivo, pendiente }

Estado e = Estado.activo;
print(e.name);    // "activo"
print(e.index);   // 0

if (e == Estado.activo) {
  print("activo!");
}

enum define un conjunto fijo de valores. .name devuelve el nombre como string y .index la posición (empieza en 0).

Operador super
class Animal {
  final String nombre;
  Animal(this.nombre);
}

class Perro extends Animal {
  final String raca;
  Perro(String nombre, this.raca) : super(nombre);
}

var c = Perro("Rex", "Labrador");

super(...) llama al constructor de la superclase. En Dart 2.17+ puedes usar super.nombre directamente en los parámetros.

Factory constructor
class Logger {
  static final Logger _inst = Logger._();
  Logger._();

  factory Logger() => _inst;

  void log(String msg) => print(msg);
}

var a = Logger();
var b = Logger(); // misma instancia

Un constructor factory controla la creación del objeto, pudiendo devolver instancias existentes (singleton) o de subclases.

Coleções


12 cards
List (lista)
var lista = [1, 2, 3];
lista.add(4);        // añade
lista.remove(1);     // quita el valor 1
lista.removeAt(0);   // quita por índice
lista.length;        // tamaño
lista[0];            // acceso por índice
lista.first; lista.last;

La List es una colección ordenada y dinámica. add() inserta al final, remove() elimina por valor y el índice accede a los elementos.

any() y every()
var lista = [1, 2, 3, 4];

lista.any((e) => e > 3);   // true (alguno)
lista.every((e) => e > 0); // true (todos)
lista.contains(2);         // true
lista.isEmpty;             // false

any() verifica si al menos uno de los elementos cumple la condición y every() si todos la cumplen.

Collection-if y for
bool activo = true;
var ítems = [
  "base",
  if (activo) "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. Muy usado en el children de widgets.

Map (mapa)
var mapa = {"a": 1, "b": 2};
mapa["c"] = 3;          // añade/actualiza
mapa.remove("a");       // quita la clave
mapa.keys;              // ("b", "c")
mapa.values;            // (2, 3)
mapa.containsKey("b");  // true
mapa["x"] ??= 0;        // solo si no existe

El Map guarda pares clave-valor. Acceder a una clave inexistente devuelve null. keys y values devuelven iterables.

fold() y reduce()
var lista = [1, 2, 3, 4];

int suma = lista.fold(0, (acc, e) => acc + e);
// 10

int producto = lista.reduce((acc, e) => acc * e);
// 24

fold() acumula un valor a partir de uno inicial. reduce() hace lo mismo pero usa el primer elemento como valor inicial.

List.generate()
var quadrados = List.generate(5, (i) => i * i);
// [0, 1, 4, 9, 16]

var pares = List.generate(10, (i) => i * 2);
// [0, 2, 4, ..., 18]

List.generate() crea una lista a partir de una función que recibe el índice. Ideal para generar secuencias o datos de prueba.

Set (conjunto)
var conjunto = {1, 2, 3};
conjunto.add(3);        // ignorado (duplicado)
conjunto.add(4);
conjunto.contains(1);   // true
conjunto.length;        // 4

var uniao = {1, 2}.union({2, 3}); // {1,2,3}

El Set guarda valores únicos sin orden garantizado. Añadir un duplicado no tiene efecto. Útil para eliminar repeticiones.

sort() y reversed
var lista = [3, 1, 4, 1, 5];
lista.sort();              // [1, 1, 3, 4, 5]
lista.sort((a, b) => b.compareTo(a)); // desc

var nombres = ["ana", "rui"];
nombres.reversed.toList();   // ["rui", "ana"]

sort() ordena la lista en el lugar (muta la original). Pasa un comparador para un orden personalizado. reversed devuelve el orden inverso.

forEach e indexed
var frutas = ["manzana", "pera"];
frutas.forEach((f) => print(f));

// Con índice (Dart 3)
for (var (i, f) in frutas.indexed) {
  print("$i: $f");
}

forEach() ejecuta una función para cada elemento. .indexed proporciona pares (índice, valor) para iterar con posición.

map() y where()
var lista = [1, 2, 3, 4, 5];

var doble = lista.map((e) => e * 2).toList();
// [2, 4, 6, 8, 10]

var pares = lista.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>? nula;
var c = [...?nula, 1]; // spread null-safe

var mapa = {...outroMapa, "x": 1};

... expande los elementos de una colección dentro de otra. ...? evita el error si la colección es null.

Map.fromEntries y transformaciones
var mapa = {"a": 1, "b": 2};

var dobrado = mapa.map((k, v) => MapEntry(k, v * 2));
// {"a": 2, "b": 4}

var lista = mapa.entries.toList();

El map() de un Map devuelve un MapEntry para transformar claves y valores. entries expone los pares como iterable.

Async e Streams


12 cards
async / await
Future<String> buscar() async {
  var r = await http.get(url);
  return r.body;
}

void main() async {
  var datos = await buscar();
  print(datos);
}

async marca una función asíncrona y await pausa la ejecución hasta que el Future se complete, manteniendo el código legible.

Future.wait (paralelo)
var resultados = await Future.wait([
  buscarA(),
  buscarB(),
  buscarC(),
]);
print(resultados); // [a, b, c]

Future.wait() ejecuta varios Future en paralelo y espera por todos. Más rápido que un await secuencial cuando son independientes.

onError y catchError
// Con catchError
buscar().catchError((e) {
  print("Falhou: $e");
  return "fallback";
});

// Stream con onError
stream.listen(
  (d) => print(d),
  onError: (e) => print(e),
);

catchError() trata errores de un Future y puede devolver un valor alternativo. En listen() usa el parámetro onError.

Future
Future<String> tarea() {
  return Future.delayed(
    Duration(seconds: 2),
    () => "pronto",
  );
}

tarea().then((v) => print(v));

Un Future<T> representa un valor disponible en el futuro. .then() registra un callback que se ejecuta 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(). .listen() se suscribe a los eventos.

async* y yield*
Stream<int> ate(int n) async* {
  for (var i = 0; i <= n; i++) {
    yield i;
  }
}

Stream<int> doble() async* {
  yield* ate(3).map((e) => e * 2);
}

async* devuelve una Stream y yield emite valores. yield* reemite todos los valores de otra stream.

try / catch async
try {
  var datos = await buscar();
  print(datos);
} catch (e) {
  print("Error: $e");
} finally {
  print("terminado");
}

Los errores en código await se capturan con un try/catch normal. finally se ejecuta siempre, con o sin error.

StreamBuilder (widget)
StreamBuilder<int>(
  stream: contar(),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text("${snapshot.data}");
    }
    return CircularProgressIndicator();
  },
)

El StreamBuilder reconstruye la UI cada vez que la stream emite un valor. El snapshot contiene el estado y los datos actuales.

sync* y generators
Iterable<int> fib(int n) sync* {
  int a = 0, b = 1;
  for (var i = 0; i < n; i++) {
    yield a;
    [a, b] = [b, a + b];
  }
}

print(fib(6).toList()); // [0,1,1,2,3,5]

sync* crea un generador síncrono que devuelve un Iterable. Los valores se producen de forma perezosa (lazy) al iterar.

Stream
Stream<int> contar() async* {
  for (var i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

await for (var n in contar()) {
  print(n);
}

Una Stream emite varios valores a lo largo del tiempo. async* con yield produce los valores y await for los consume.

FutureBuilder (widget)
FutureBuilder<String>(
  future: buscar(),
  builder: (context, snapshot) {
    if (snapshot.connectionState ==
        ConnectionState.done) {
      return Text(snapshot.data ?? "");
    }
    return CircularProgressIndicator();
  },
)

El FutureBuilder muestra un estado de carga mientras el Future no completa y la UI final cuando termina. Ideal para peticiones HTTP.

Timeout y retry
try {
  var r = await buscar()
      .timeout(Duration(seconds: 5));
} on TimeoutException {
  print("Demorou demais");
}

// Reintentar en streams
stream.timeout(Duration(seconds: 3));

timeout() lanza una TimeoutException si el Future no completa a tiempo. Esencial para peticiones de red robustas.

Widgets e Layout


18 cards
StatelessWidget
class Hola extends StatelessWidget {
  const Hola({super.key});

  @override
  Widget build(BuildContext context) {
    return Text("Hola Mundo");
  }
}

Un StatelessWidget es inmutable: describe la UI a partir de sus parámetros. El método build() se llama siempre que el widget necesita redibujarse.

ListView.builder
ListView.builder(
  itemCount: ítems.length,
  itemBuilder: (context, i) {
    return ListTile(
      title: Text(ítems[i]),
    );
  },
)

El ListView.builder crea los ítems de forma perezosa (lazy), construyendo solo los visibles. Esencial para listas grandes con buen rendimiento.

Iconos
Icon(Icons.favorite, color: Colors.red, size: 32)
Icon(Icons.add)
IconButton(
  icon: Icon(Icons.delete),
  onPressed: () {},
)

El widget Icon muestra iconos Material de Icons. El IconButton hace el icono clicable con un callback onPressed.

Card y ListTile
Card(
  elevation: 4,
  child: ListTile(
    leading: Icon(Icons.person),
    title: Text("Ana Silva"),
    subtitle: Text("Programadora"),
    trailing: Icon(Icons.arrow_forward),
    onTap: () {},
  ),
)

El Card es una superficie Material con sombra. El ListTile es una fila estándar con leading, title, subtitle y trailing.

Spacer y Divider
Row(
  children: [
    Text("Izquierda"),
    Spacer(),         // empuja hacia los lados
    Text("Derecha"),
  ],
)

Divider(thickness: 1, color: Colors.grey)

El Spacer ocupa todo el espacio libre en una Row/Column. El Divider dibuja una línea separadora horizontal.

Scaffold
Scaffold(
  appBar: AppBar(title: Text("Título")),
  body: Center(child: Text("Contenido")),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: Icon(Icons.add),
  ),
)

El Scaffold es la estructura base de una pantalla Material. Define appBar, body, floatingActionButton, drawer y bottomNavigationBar.

GestureDetector
GestureDetector(
  onTap: () => print("toque"),
  onDoubleTap: () => print("duplo"),
  onLongPress: () => print("largo"),
  child: Text("Toca aqui"),
)

El GestureDetector detecta gestos como onTap, onDoubleTap y onLongPress en cualquier widget hijo.

Stack y Positioned
Stack(
  children: [
    Image.asset("assets/fondo.png"),
    Positioned(
      bottom: 10,
      right: 10,
      child: Text("Legenda"),
    ),
  ],
)

La Stack superpone widgets en capas. El Positioned posiciona un hijo con coordenadas absolutas dentro de la stack.

GridView
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 8,
    mainAxisSpacing: 8,
  ),
  itemCount: ítems.length,
  itemBuilder: (context, i) => Card(),
)

El GridView.builder crea una cuadrícula con scroll. crossAxisCount define el numero de columnas y los spacing los espaciados.

SafeArea
SafeArea(
  child: Column(
    children: [
      Text("Contenido seguro"),
    ],
  ),
)

El SafeArea añade padding para evitar el notch, la barra de estado y los botones del sistema. Garantiza que el contenido queda en el área visible.

Column y Row
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text("A"),
    Text("B"),
  ],
)

Una Column organiza los hijos en vertical y una Row en horizontal. mainAxisAlignment alinea en el eje principal y crossAxisAlignment en el transversal.

Text y TextStyle
Text(
  "Hola Flutter",
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
  ),
  textAlign: TextAlign.center,
)

Text muestra texto y TextStyle controla fuente, tamaño, peso y color. textAlign define la alineación horizontal.

Expanded y Flexible
Row(
  children: [
    Expanded(flex: 2, child: Text("A")),
    Expanded(flex: 1, child: Text("B")),
  ],
)

El Expanded hace que el hijo ocupe el espacio disponible en una Row o Column. flex define la proporción entre varios expanded.

Wrap
Wrap(
  spacing: 8,
  runSpacing: 8,
  children: [
    Chip(label: Text("Dart")),
    Chip(label: Text("Flutter")),
    Chip(label: Text("Firebase")),
  ],
)

El Wrap dispone los hijos en línea y salta a la siguiente línea cuando no caben. Ideal para tags y chips de ancho variable.

Container
Container(
  width: 100,
  height: 100,
  padding: EdgeInsets.all(8),
  margin: EdgeInsets.all(16),
  color: Colors.blue,
  child: Text("Hola"),
)

El Container es una caja versátil con dimensiones, padding, margin, color y decoración. Combina varios widgets de layout en uno solo.

Imágenes
Image.asset("assets/foto.png", width: 200)
Image.network("https://site.com/img.jpg")
Image.file(File("/camino/foto.png"))

CircleAvatar(
  backgroundImage: AssetImage("assets/perfil.png"),
)

Image.asset carga desde assets locales, Image.network desde una URL e Image.file desde el dispositivo. Registra los assets en pubspec.yaml.

Padding y SizedBox
Padding(
  padding: EdgeInsets.symmetric(
    horizontal: 16, vertical: 8),
  child: Text("Hola"),
)

SizedBox(height: 20) // espaciado
SizedBox(width: 100, child: botón)

El Padding añade espacio interno con EdgeInsets. El SizedBox crea espaciados fijos o fuerza dimensiones en un hijo.

SingleChildScrollView
SingleChildScrollView(
  child: Column(
    children: [
      Text("A"),
      Text("B"),
      Text("C"),
    ],
  ),
)

El SingleChildScrollView añade scroll a un único hijo. Se usa cuando el contenido puede exceder la pantalla y para evitar overflow.

Estado e Navegação


14 cards
StatefulWidget
class Contador extends StatefulWidget {
  const Contador({super.key});

  @override
  State<Contador> createState() => _ContadorState();
}

class _ContadorState extends State<Contador> {
  int n = 0;

  @override
  Widget build(BuildContext context) {
    return Text("$n");
  }
}

Un StatefulWidget tiene estado mutable guardado en una clase State. createState() enlaza el widget con su estado persistente.

Navegación (push)
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (_) => DetalhePage(),
  ),
);

Navigator.push() apila una nueva pantalla. MaterialPageRoute define la transición y el builder crea la página de destino.

showDialog
showDialog(
  context: context,
  builder: (_) => AlertDialog(
    title: Text("Atención"),
    content: Text("Confirma a acción?"),
    actions: [
      TextButton(
        onPressed: () => Navigator.pop(context),
        child: Text("OK"),
      ),
    ],
  ),
);

showDialog() muestra un AlertDialog modal. Los actions contienen botones y Navigator.pop() cierra el cuadro.

InheritedWidget / Theme
// Acceder al tema en cualquier widget
Theme.of(context).primaryColor;
MediaQuery.of(context).size;

// Datos compartidos vía InheritedWidget
var datos = Dependencia.of(context);

El InheritedWidget comparte datos por el árbol de widgets. Theme.of() y MediaQuery.of() son ejemplos clásicos de este patrón.

setState
ElevatedButton(
  onPressed: () {
    setState(() {
      n++;
    });
  },
  child: Text("Incrementar"),
)

setState() actualiza el estado y marca el widget para reconstrucción. Solo debe llamarse dentro de una clase State.

Navegación (pop)
Navigator.pop(context);

// Devolver resultado
Navigator.pop(context, "valor");

// Recibir en el push
var r = await Navigator.push(context, rota);

Navigator.pop() cierra la pantalla actual. Puede devolver un resultado que recibe el await del push() que la abrió.

showModalBottomSheet
showModalBottomSheet(
  context: context,
  builder: (_) => Container(
    height: 200,
    child: Center(child: Text("Opciones")),
  ),
);

showModalBottomSheet() abre un panel desde la parte inferior de la pantalla. Muy usado para menús de acciones y selecciones.

ValueNotifier y ValueListenableBuilder
final contador = ValueNotifier<int>(0);

ValueListenableBuilder<int>(
  valueListenable: contador,
  builder: (context, valor, _) {
    return Text("$valor");
  },
)

contador.value++; // actualiza la UI

El ValueNotifier guarda un valor notificable. El ValueListenableBuilder reconstruye solo la parte afectada cuando el .value cambia, sin setState.

Ciclo de vida (State)
class _PaginaState extends State<Pagina> {
  @override
  void initState() {
    super.initState(); // 1º: inicialización
  }

  @override
  void dispose() {
    super.dispose(); // último: limpieza
  }
}

initState() se ejecuta una vez en la creación (ideal para subscriptions) y dispose() en la destrucción (libera recursos y controllers).

Rutas nombradas
MaterialApp(
  initialRoute: "/",
  routes: {
    "/": (_) => Home(),
    "/detalle": (_) => Detalle(),
  },
)

Navigator.pushNamed(context, "/detalle");

Define las rutas en el routes del MaterialApp y navega por nombre con pushNamed(). Más organizado para apps con muchas pantallas.

SnackBar
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text("Guardado!"),
    duration: Duration(seconds: 2),
    action: SnackBarAction(
      label: "Deshacer",
      onPressed: () {},
    ),
  ),
);

El SnackBar muestra un mensaje temporal en la parte inferior. Se muestra vía ScaffoldMessenger y puede tener una SnackBarAction.

didUpdateWidget
@override
void didUpdateWidget(covariant MeuWidget old) {
  super.didUpdateWidget(old);
  if (old.id != widget.id) {
    reload();
  }
}

didUpdateWidget() se llama cuando el widget padre cambia y el State se reutiliza. Compara el widget antiguo con el nuevo vía widget.

Pasar argumentos
// Enviar
Navigator.pushNamed(
  context, "/detalle",
  arguments: {"id": 42},
);

// Recibir
final args = ModalRoute.of(context)!
    .settings.arguments as Map;

El arguments del pushNamed() envía datos a la ruta. En la página de destino se obtienen vía ModalRoute.of(context).settings.arguments.

pushReplacement
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (_) => Login()),
);

// Limpiar toda la pila
Navigator.pushAndRemoveUntil(
  context, rota, (r) => false,
);

pushReplacement() sustituye la pantalla actual (sin volver). pushAndRemoveUntil() apila y elimina pantallas anteriores según un predicado.

CLI, Temas e Boas Práticas


12 cards
Crear y ejecutar el proyecto
flutter create mi_app
cd mi_app
flutter run

# Ejecutar en un dispositivo específico
flutter run -d chrome
flutter devices

flutter create genera la estructura del proyecto y flutter run compila y ejecuta con hot reload. -d elige el dispositivo.

Theme y colores
MaterialApp(
  theme: ThemeData(
    primarySwatch: Colors.blue,
    brightness: Brightness.light,
  ),
)

// Acceder en cualquier widget
Theme.of(context).primaryColor;
Color(0xFF6200EE);
Colors.blue.shade700;

El ThemeData define colores y estilos globales. Theme.of(context) accede al tema. Los colores usan Colors o hexadecimal Color(0xFF...).

Estructura de carpetas
lib/
  main.dart
  models/       # clases de datos
  services/     # API, lógica
  screens/      # páginas
  widgets/      # widgets reutilizables
  theme/        # estilos

Organiza el código en models, services, screens y widgets. Separa la UI de la lógica de negocio para el mantenimiento.

Comandos CLI útiles
flutter pub get      # instala dependencias
flutter pub add http # añade un paquete
flutter clean        # limpia la build
flutter build apk    # genera un APK
flutter build appbundle
flutter doctor       # verifica el entorno

flutter pub gestiona dependencias. flutter clean resuelve problemas de caché y flutter doctor diagnostica la instalación.

MediaQuery (dimensiones)
final size = MediaQuery.of(context).size;
double ancho = size.width;
double altura = size.height;

double padding = MediaQuery.of(context)
    .padding.top; // barra de estado

El MediaQuery proporciona dimensiones de la pantalla e información del dispositivo. Esencial para layouts responsivos y adaptar la UI.

Buenas prácticas
// 1. Widgets pequeños y reutilizables
// 2. const siempre que sea posible
// 3. Separar UI de la lógica
// 4. Evitar setState en exceso
// 5. Liberar controllers en dispose()

Prefiere widgets pequeños, usa const, separa la lógica de la UI y libera recursos en el dispose(). Evita reconstrucciones innecesarias.

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0
  provider: ^6.1.0

flutter:
  assets:
    - assets/
    - assets/imágenes/

El pubspec.yaml declara dependencias y assets. Tras cambiarlo, ejecuta flutter pub get. El ^ acepta actualizaciones compatibles.

LayoutBuilder (responsivo)
LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return Text("Pantalla largo");
    }
    return Text("Pantalla estrecho");
  },
)

El LayoutBuilder da acceso a las constraints del padre, permitiendo adaptar el layout al espacio disponible (responsividad).

Debug y print
print("valor: $x");
debugPrint("texto largo...");

// Flutter DevTools (inspector de widgets)
flutter run
# abre DevTools en el terminal

assert(x > 0, "x debe ser positivo");

Usa print() para logs simples y debugPrint() para textos anchos. Flutter DevTools inspecciona widgets, rendimiento y red.

Hot reload y restart
# Durante flutter run:
r   # hot reload (rápido, mantiene el estado)
R   # hot restart (reinicia el estado)
q   # salir
o   # alternar plataforma

El hot reload (r) aplica cambios de UI manteniendo el estado. El hot restart (R) reinicia el estado de la app por completo.

const (rendimiento)
// Widget constante (no se reconstruye)
const Text("Hola")
const SizedBox(height: 10)
const Icon(Icons.star)

// Constructor const
class Título extends StatelessWidget {
  const Título({super.key});
}

Marca los widgets inmutables con const para que Flutter los reutilice y evite reconstrucciones innecesarias, mejorando el rendimiento.

Assets y fuentes
# pubspec.yaml
flutter:
  assets:
    - assets/logo.png
  fonts:
    - family: Roboto
      fonts:
        - asset: fonts/Roboto.ttf

// Uso
Image.asset("assets/logo.png")
TextStyle(fontFamily: "Roboto")

Registra assets y fuentes en el pubspec.yaml para incluirlos en la build. Accede con Image.asset() y fontFamily en el TextStyle.

Formulários e Input


12 cards
TextField básico
TextField(
  decoration: InputDecoration(
    labelText: "Nombre",
    hintText: "Escreve o tu nombre",
    border: OutlineInputBorder(),
  ),
  onChanged: (texto) => print(texto),
)

El TextField recibe texto del usuario. El InputDecoration define label, hint y borde. El onChanged se dispara en cada cambio.

Teclado y foco
TextField(
  keyboardType: TextInputType.number,
  obscureText: true,        // password
  textInputAction: TextInputAction.next,
  autofocus: true,
)

// Ocultar el teclado
FocusScope.of(context).unfocus();

El keyboardType elige el teclado, obscureText oculta caracteres (contraseña) y textInputAction define el botón de acción. unfocus() cierra el teclado.

DropdownButton
String? país;

DropdownButton<String>(
  value: país,
  hint: Text("Escolhe um país"),
  items: ["Portugal", "Brasil"]
      .map((p) => DropdownMenuItem(
            value: p,
            child: Text(p),
          ))
      .toList(),
  onChanged: (v) => setState(() => país = v),
)

El DropdownButton muestra una lista desplegable. Los items son DropdownMenuItem y el onChanged recibe el valor seleccionado.

TextEditingController
final controller = TextEditingController();

TextField(controller: controller);

// Leer y definir el valor
print(controller.text);
controller.text = "nuevo valor";
controller.clear();

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

El TextEditingController permite leer y controlar el texto programáticamente. Debe liberarse en el dispose() para evitar memory leaks.

Checkbox
bool aceite = false;

CheckboxListTile(
  title: Text("Aceito os termos"),
  value: aceite,
  onChanged: (v) {
    setState(() => aceite = v ?? false);
  },
)

El Checkbox alterna un bool. El CheckboxListTile une la casilla con un texto. Actualiza el estado en el onChanged.

Slider
double volume = 0.5;

Slider(
  value: volume,
  min: 0,
  max: 1,
  divisions: 10,
  label: volume.toStringAsFixed(1),
  onChanged: (v) => setState(() => volume = v),
)

El Slider selecciona un valor en un intervalo. min/max definen los límites, divisions los pasos y label el valor mostrado.

Form y validación
final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: Column(children: [
    TextFormField(
      validator: (v) =>
          v!.isEmpty ? "Obligatorio" : null,
    ),
    ElevatedButton(
      onPressed: () {
        if (_formKey.currentState!.validate()) {
          // válido
        }
      },
      child: Text("Enviar"),
    ),
  ]),
)

El Form agrupa campos con una GlobalKey<FormState>. El validator devuelve un mensaje de error o null si es válido. validate() los verifica todos.

Radio
String opción = "a";

RadioListTile(
  title: Text("Opción A"),
  value: "a",
  groupValue: opción,
  onChanged: (v) => setState(() => opción = v!),
)

El Radio selecciona una opción en un grupo. El groupValue es el valor actual y value el de este botón. Solo uno queda activo por grupo.

DatePicker
final data = await showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2000),
  lastDate: DateTime(2100),
);

if (data != null) print(data);

showDatePicker() abre un selector de fecha y devuelve un DateTime?. Define initialDate, firstDate y lastDate.

TextFormField
TextFormField(
  decoration: InputDecoration(labelText: "Email"),
  keyboardType: TextInputType.emailAddress,
  validator: (v) {
    if (v == null || !v.contains("@")) {
      return "Email inválido";
    }
    return null;
  },
)

El TextFormField es un TextField integrado en un Form, con validator. El keyboardType ajusta el teclado (email, numero, etc.).

Switch
bool activo = true;

SwitchListTile(
  title: Text("Notificaciones"),
  value: activo,
  onChanged: (v) {
    setState(() => activo = v);
  },
)

El Switch es un interruptor de encendido/apagado. El SwitchListTile lo combina con una etiqueta. Actualiza el bool en el onChanged.

FocusNode
final foco = FocusNode();

TextField(focusNode: foco);

// Dar foco programáticamente
foco.requestFocus();

@override
void dispose() {
  foco.dispose();
  super.dispose();
}

El FocusNode controla el foco de un campo. requestFocus() lo activa programáticamente. Libéralo en el dispose().