Cheatsheet Flutter
Framework para desenvolvimento mobile
Flutter
Sintaxe Básica (Dart)
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"); // reemplazaLas 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}; // conjuntoDart 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
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,6continue 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 0while 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 vezdo-while ejecuta el bloque antes de verificar la condición, garantizando al menos una ejecución. Útil para menús y validaciones.
Funções
Función simple
int sumar(int a, int b) {
return a + b;
}
void saludo(String nombre) {
print("Hola $nombre");
}
print(sumar(2, 3)); // 5Declara 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 = 0Los 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)); // 6Las 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] infoLos 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)); // 15Una 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 á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.
Classes e OOP
Clase básica
class Persona {
String nombre;
int edad;
Persona(this.nombre, this.edad);
}
var p = Persona("Ana", 30);
print(p.nombre); // AnaEl 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.8Los 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); // trueUna 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 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 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 auextends 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 instanciaUn constructor factory controla la creación del objeto, pudiendo devolver instancias existentes (singleton) o de subclases.
Coleções
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 existeEl 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
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
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
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 UIEl 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
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 estadoEl 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
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().