Un widget es el bloque básico de construcción en Flutter: todo lo que ves en la pantalla de una app es un widget.
Botones, textos, imágenes, columnas, filas — todos son widgets. Flutter construye la interfaz completa combinando widgets dentro de otros widgets.
¿Por qué todo es un widget?
En Flutter no existe separación entre "componentes visuales" y "componentes de lógica". Todo se modela como widget. Esta decisión hace que el código sea predecible y fácil de leer.
Cada widget describe cómo debe verse su parte de la interfaz. Cuando algo cambia, Flutter reconstruye solo los widgets necesarios.
Dos tipos fundamentales de widgets
Flutter divide los widgets en dos categorías principales: StatelessWidget y StatefulWidget.
StatelessWidget
Un StatelessWidget es un widget que no cambia después de ser creado.
Muestra información fija. No reacciona a eventos del usuario ni actualiza su contenido en pantalla.
Úsalo cuando la información que muestra no varía. Por ejemplo: un encabezado con el logo de Bimbo, una etiqueta de precio estático o un texto de bienvenida.
Estructura básica de un StatelessWidget:
class MiEncabezado extends StatelessWidget {
const MiEncabezado({super.key});
@override
Widget build(BuildContext context) {
return Text("Bienvenido a la app de Bimbo");
}
}
El método build regresa el widget que se mostrará en pantalla. Flutter llama a build una sola vez para los widgets estáticos.
StatefulWidget
Un StatefulWidget es un widget que puede cambiar su apariencia en respuesta a eventos o datos.
Cuando el usuario presiona un botón, llena un formulario o se carga información de un servidor, usas StatefulWidget. Tiene un objeto State separado que guarda los datos cambiantes.
Estructura básica de un StatefulWidget:
class ContadorProductos extends StatefulWidget {
const ContadorProductos({super.key});
@override
State<ContadorProductos> createState() => _ContadorProductosState();
}
class _ContadorProductosState extends State<ContadorProductos> {
int cantidad = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("Productos en carrito: $cantidad"),
ElevatedButton(
onPressed: () {
setState(() {
cantidad++;
});
},
child: Text("Agregar"),
),
],
);
}
}
El método setState() le avisa a Flutter que algo cambió. Flutter vuelve a llamar build y actualiza la pantalla.
Widgets de estructura: Column y Row
Para acomodar widgets en pantalla necesitas widgets de layout. Los más usados son Column y Row.
| Widget | Dirección | Ejemplo de uso |
|---|---|---|
Column |
Vertical (de arriba a abajo) | Lista de productos |
Row |
Horizontal (de izquierda a derecha) | Íconos de una barra de navegación |
Container |
Un solo hijo con estilo | Caja con color de fondo |
SizedBox |
Espacio vacío | Separar dos elementos |
Ejemplo con Column:
Column(
children: [
Text("Oferta del día — Liverpool"),
Text("Televisor 55 pulgadas"),
Text("Precio: $8,999"),
],
)
Cada elemento de la lista children es otro widget. Así se "anidan" widgets para construir la interfaz completa.
Ejemplo práctico: tarjeta de producto
Imagina que construyes una pantalla para la app de Mercado Libre México. Necesitas mostrar una tarjeta con nombre del producto, vendedor y precio.
class TarjetaProducto extends StatelessWidget {
final String nombre;
final String vendedor;
final double precio;
const TarjetaProducto({
super.key,
required this.nombre,
required this.vendedor,
required this.precio,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(12),
margin: EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(color: Colors.black12, blurRadius: 4),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
nombre,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
SizedBox(height: 4),
Text("Vendedor: $vendedor"),
SizedBox(height: 4),
Text(
"\$${precio.toStringAsFixed(0).replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(m) => '${m[1]},',
)}",
style: TextStyle(color: Colors.green, fontSize: 18),
),
],
),
);
}
}
Este widget recibe tres parámetros nombrados (nombre, vendedor, precio). Como aprendiste en la lección anterior, Flutter usa intensamente los parámetros nombrados. El widget es Stateless porque la tarjeta solo muestra datos; no los modifica.
Cómo usarlo en pantalla:
TarjetaProducto(
nombre: "Cafetera FEMSA Premium",
vendedor: "TiendaFEMSA_Oficial",
precio: 1299,
)
En pantalla, el texto del precio aparece como $1,299 en color verde, igual al formato que usan las apps comerciales mexicanas.
Ejemplo con StatefulWidget: contador de artículos
Ahora construye un widget que permite al usuario incrementar o decrementar la cantidad de un producto antes de agregarlo al carrito.
class SelectorCantidad extends StatefulWidget {
const SelectorCantidad({super.key});
@override
State<SelectorCantidad> createState() => _SelectorCantidadState();
}
class _SelectorCantidadState extends State<SelectorCantidad> {
int cantidad = 1;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
if (cantidad > 1) {
setState(() {
cantidad--;
});
}
},
),
Text(
"$cantidad",
style: TextStyle(fontSize: 20),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
cantidad++;
});
},
),
],
);
}
}
Cada vez que el usuario presiona + o -, setState actualiza cantidad y Flutter redibuja solo este widget. El resto de la pantalla no se toca.
Errores comunes
1. Olvidar setState en un StatefulWidget
Si cambias una variable sin llamar setState, la pantalla no se actualiza aunque el valor cambie en memoria.
// ❌ Incorrecto: la pantalla no cambia
onPressed: () {
cantidad++;
}
// ✅ Correcto
onPressed: () {
setState(() {
cantidad++;
});
}
2. Usar StatefulWidget cuando no es necesario
Muchos principiantes usan StatefulWidget para todo. Si el widget solo muestra texto fijo, usa StatelessWidget. El código queda más limpio y Flutter lo procesa más rápido.
3. No pasar const al constructor cuando es posible
Cuando un widget es completamente estático, declararlo con const mejora el rendimiento.
// ❌ Sin const
Text("Oferta del día")
// ✅ Con const
const Text("Oferta del día")
Flutter reutiliza el objeto en memoria en lugar de crearlo de nuevo en cada build.
4. Anidar Column dentro de Column sin límite de altura
Si pones un Column dentro de otro Column con hijos que se expanden, Flutter lanza un error de desbordamiento. Usa Expanded o Flexible para controlar el espacio disponible.
Widgets de contenido más usados
| Widget | Función |
|---|---|
Text |
Muestra texto en pantalla |
Image |
Muestra una imagen local o de red |
Icon |
Muestra un ícono del paquete Material |
ElevatedButton |
Botón con relieve, para acciones principales |
TextField |
Campo de entrada de texto |
Checkbox |
Casilla de selección |
CircularProgressIndicator |
Indicador de carga |
Estos widgets son los que más vas a combinar en tus primeras apps.
Resumen
Los widgets son la unidad mínima de Flutter. Cada pantalla es un árbol de widgets anidados. Elige StatelessWidget para contenido fijo y StatefulWidget cuando los datos cambian. Llama siempre setState para que Flutter sepa que debe redibujar. Con Column, Row y Container puedes construir la mayoría de las interfaces que necesitas en tus primeras apps.