Variables y Tipos de Datos
En Dart, las variables son contenedores para almacenar valores. Como Dart es un lenguaje con strong typing (tipado fuerte), cada variable tiene un tipo asociado que determina qué clase de valores puede contener. Sin embargo, gracias a la type inference (inferencia de tipos), no siempre es necesario declarar el tipo de forma explícita.
Declaración de Variables
En Dart existen dos formas principales de declarar una variable:
- Tipado implícito (Inferencia de tipos): Utilizando la palabra clave
var. El compilador infiere el tipo de dato según el valor inicial asignado. - Tipado explícito: Declarando directamente el tipo de dato (
int,String,double,bool, etc.).
void main() {
// Tipado implícito (var)
var nombre = 'Icesi'; // Inferred as String
var fundacion = 1979; // Inferred as int
// Tipado explícito
String estudiante = 'Juan';
double promedio = 4.5;
bool estaMatriculado = true;
print('Universidad: $nombre ($fundacion)');
print('Estudiante: $estudiante con promedio $promedio');
}
Una vez que Dart infiere el tipo de una variable declarada con var, no puedes asignarle un valor de otro tipo más adelante. Por ejemplo, si haces var edad = 20;, intentar hacer edad = 'veinte'; lanzará un error de compilación.
Dynamic
Si necesitas que una variable cambie de tipo dinámicamente durante el tiempo de ejecución (runtime), puedes usar el tipo dynamic.
void main() {
dynamic variableFlexible = 'Hola estudiantes';
print(variableFlexible); // Imprime un String
variableFlexible = 42;
print(variableFlexible); // Imprime un int (permitido con dynamic)
variableFlexible = [1, 2, 3];
print(variableFlexible); // Imprime una List (permitido con dynamic)
}
Evita abusar de dynamic. Al usarlo, desactivas el análisis estático del compilador (static type checking), lo que incrementa el riesgo de errores en tiempo de ejecución (runtime errors). Utilízalo únicamente cuando sea estrictamente necesario.
Final vs Const
Tanto final como const se utilizan para definir variables cuyos valores no pueden cambiar una vez asignados (inmutabilidad). Sin embargo, hay una diferencia crucial en el momento en que se evalúan sus valores:
Final
Una variable final se define en tiempo de ejecución (runtime). Esto significa que puedes asignarle un valor que se calcula o se obtiene dinámicamente al ejecutar la aplicación (por ejemplo, el resultado de una petición HTTP, la hora actual o un valor ingresado por el usuario).
void main() {
final DateTime fechaActual = DateTime.now(); // Se calcula al ejecutar
print(fechaActual);
// fechaActual = DateTime.now(); // ERROR: No se puede reasignar
}
Const
Una variable const se define en tiempo de compilación (compile-time). Su valor debe conocerse antes de que la aplicación se ejecute. Todo valor const es implícitamente final.
void main() {
const double pi = 3.14159; // Valor conocido en compilación
const double areaCirculo = pi * 10 * 10; // Operación computable en compilación
print('Área: $areaCirculo');
// const DateTime ahora = DateTime.now(); // ERROR: DateTime.now() requiere ejecutarse
}
En Flutter, usar widgets marcados como const es una excelente práctica de optimización de rendimiento. Le indica al framework que ese widget no cambiará y evita reconstrucciones (re-renders) innecesarias en la interfaz.
Late
La palabra clave late le promete al compilador de Dart que inicializarás una variable no-nula antes de que sea leída por primera vez. Es útil cuando:
- El valor de la variable se inicializa después de su declaración (por ejemplo, en un método de ciclo de vida como
initStateen Flutter). - La inicialización es costosa y quieres realizarla de manera perezosa (lazy initialization), es decir, solo cuando la variable se acceda por primera vez.
class Profesor {
late String materia;
void asignarMateria(String nombreMateria) {
materia = nombreMateria;
}
}
void main() {
var profe = Profesor();
// print(profe.materia); // ERROR en runtime: LateInitializationError (no ha sido asignada)
profe.asignarMateria('Desarrollo Móvil');
print(profe.materia); // Funciona correctamente
}
Null Safety
Dart implementa Sound Null Safety (seguridad contra nulos garantizada). Esto significa que, por defecto, los tipos de datos no pueden contener valores nulos (null). Esto previene el clásico error del millón de dólares: el NullPointerException.
Tipos Nullable y Non-nullable
Para permitir que una variable acepte null, debes agregar un signo de interrogación ? al final del tipo de dato:
void main() {
String nombreObligatorio = 'Ana'; // Non-nullable por defecto
// nombreObligatorio = null; // ERROR de compilación
String? nombreOpcional = null; // Nullable
nombreOpcional = 'Carlos'; // Permitido
nombreOpcional = null; // Permitido
}
Operadores de Null Safety
Dart proporciona operadores convenientes para trabajar con variables que pueden ser nulas:
- Operador de acceso condicional (
?.): Si el objeto es nulo, la expresión devuelve nulo en lugar de lanzar una excepción. - Operador de aserción nula (
!): Fuerza a tratar una variable nullable como si fuera non-nullable. Lanza una excepción si el valor resulta ser nulo en runtime. - Operador null-coalescing (
??): Proporciona un valor por defecto en caso de que la expresión a la izquierda sea nula.
void main() {
String? descripcionCurso;
// 1. Acceso condicional (?.): evita caídas si la variable es nula
print(descripcionCurso?.length); // Imprime: null
// 2. Operador null-coalescing (??): asigna valor por defecto si es nulo
String descripcionFinal = descripcionCurso ?? 'Sin descripción disponible';
print(descripcionFinal); // Imprime: Sin descripción disponible
// 3. Operador de aserción nula (!): úsalo solo si estás 100% seguro de que no es nulo
descripcionCurso = 'Clase de Flutter';
print(descripcionCurso.length); // Funciona
}
Ejercicios Prácticos
Pon a prueba tus conocimientos resolviendo los siguientes retos sobre variables, inmutabilidad y Null Safety.
Ejercicio 1: Identificación de Errores
Analiza el siguiente fragmento de código y determina cuáles líneas causarán un error de compilación y por qué.
void main() {
var counter = 10;
counter = '15';
final piValue = 3.14;
piValue = 3.1416;
const double gravity = 9.8;
const currentHour = DateTime.now().hour;
String? username;
print(username!.length);
}
Ver solución detallada
El código tiene cuatro errores de compilación y un posible error en tiempo de ejecución (runtime error):
- Línea 3 (
counter = '15';): Error de compilación. Dart infiriócountercomo tipointen su declaración. No se le puede asignar unString. - Línea 6 (
piValue = 3.1416;): Error de compilación.piValueis una variablefinal, por lo que solo se puede asignar una vez. - Línea 9 (
const currentHour = DateTime.now().hour;): Error de compilación. Las variablesconstdeben conocerse en tiempo de compilación.DateTime.now()se evalúa en tiempo de ejecución. Debería ser declarada confinal. - Línea 12 (
print(username!.length);): Error en tiempo de ejecución (Runtime Exception). Aunque compila porque usas el operador de aserción!, al ejecutar lanzará unTypeError/NullThrownErrorporqueusernameefectivamente esnullen ese punto.
Ejercicio 2: Diseñando un Sistema de Inventario Seguro
Escribe una pequeña clase en Dart que simule un producto en un inventario. Debe cumplir con las siguientes especificaciones:
- El
iddel producto debe ser inmutable y asignarse al crearlo. - El
nombredel producto debe ser obligatorio. - La
descripciondebe ser opcional (nullable). - El
preciodebe poder cambiar, pero queremos inicializarlo de manera perezosa (late) o asegurar que empiece con un valor predeterminado usando null safety. - Debe tener un método que imprima la información resumida del producto. Si no hay descripción, debe mostrar "No disponible".
Ver solución detallada
class Product {
final String id; // Inmutable
final String name; // Obligatorio
String? description; // Opcional (nullable)
late double price; // Inicialización late
Product({required this.id, required this.name, this.description});
void displayInfo() {
// Uso del operador null-coalescing (??) para la descripción
String descText = description ?? 'No disponible';
print('ID: $id | Producto: $name | Descripción: $descText | Precio: \$$price');
}
}
void main() {
var prod = Product(
id: 'PROD-100',
name: 'MacBook Air',
description: 'Chip M3, 16GB RAM'
);
// Inicializamos el precio antes de llamar a displayInfo
prod.price = 1099.99;
prod.displayInfo();
}