Para trabajar de forma profesional en un entorno con Sound Null Safety, es vital dominar los operadores que permiten interactuar con valores que pueden ser nulos sin saturar el código con bloques if. Estos operadores permiten definir valores por defecto mediante la coalescencia de nulos (??), navegar por propiedades de objetos que podrían ser nulos de forma segura (?.), o forzar la interpretación de un valor como no nulo (!) cuando tenemos la certeza lógica de su existencia. Además, facilitamos la construcción de objetos mediante el operador cascade (.. y ?..) y simplificamos la composición de colecciones con el operador spread (... y ...?). Si usas incorrectamente el operador de aserción nula (!), el programa fallará en tiempo de ejecución; si usas el cascade nulo (?..) sobre un objeto que esperabas que existiera, la lógica simplemente se ignorará silenciosamente sin lanzar una excepción.
class Report {
String? title;
List<String> logs = [];
void setTitle(String t) => title = t;
void addLog(String msg) => logs.add(msg);
@override
String toString() => 'Report(title: $title, logs: $logs)';
}
void main() {
final Map<String, dynamic> rawData = {
'title': 'Análisis de Sistema',
'tags': null,
'priority': 1,
};
// 1. Coalescencia (??) y acceso por índice ([])
// Si 'title' no existe o es null, usamos el valor por defecto
final String title = rawData['title'] ?? 'Sin título';
final int priority = rawData['priority'] ?? 0;
// 2. Spread operator (...) y Null-aware spread (...?)
// Combinamos una lista base con una que puede ser nula sin lanzar error
final List<String> baseTags = ['estático'];
final List<String>? dynamicTags = rawData['tags'] as List<String>?;
final List<String> allTags = [...baseTags, ...?dynamicTags, 'finalizado'];
// 3. Cascade (..) y Null-aware cascade (?..) [disponible desde Dart 2.12]
// Como 'report' es un objeto nullable (Report?), el uso de '..'
// daría error de compilación. Usamos '?..' para que, si es null,
// toda la cadena de llamadas se ignore silenciosamente.
Report? report;
report?..setTitle(title)
..addLog('Prioridad detectada: $priority')
..addLog('Tags procesados: $allTags');
// 4. Asignación por índice ([=])
final Map<String, String> metadata = {};
metadata['status'] = 'completado';
print('Resultado: $report');
print('Metadata: $metadata');
}
Análisis del flujo
En el ejemplo, empezamos utilizando el operador de coalescencia de nulos ?? con rawData['title']. Esto es fundamental porque, aunque el mapa sea de tipo Map<String, dynamic>, el valor recuperado puede ser null, y el operador garantiza que title sea una String no nula.
Al trabajar con las listas, el operador spread ... expande los elementos de baseTags dentro de la nueva lista. Sin embargo, la verdadera potencia está en ...?. Si intentáramos usar ...dynamicTags y el valor en el mapa fuera null, el programa lanzaría una excepción de tipo TypeError. El operador ...? verifica si el iterable es nulo antes de intentar expandirlo, permitiendo una composición de colecciones limpia y segura.
En cuanto al objeto report, nos enfrentamos a un tipo Report?. El compilador de Dart no nos permite usar el operador cascade estándar .. directamente sobre un tipo nullable porque, si report fuera null, el operador .. intentaría realizar la operación de forma insegura. Al usar ?.., estamos aplicando una protección: si report es nulo, la ejecución salta directamente al final del bloque, evitando errores de puntero nulo.
Finalmente, el uso de metadata['status'] = 'completado' emplea el operador de asignación por índice, que es la forma idiomática y eficiente de insertar o actualizar valores en un Map sin necesidad de llamar explícitamente al método .update().
El error frecuente
El error más crítico ocurre al usar el operador de aserción nula ! cuando no se tiene la certeza absoluta de que el valor no es nulo.
void procesar(String? nombre) {
// Si 'nombre' llega como null, esto lanza:
// Null check operator used on a null value
print(nombre!.length);
}
void main() {
procesar(null);
}
Este error es especialmente peligroso en aplicaciones asíncronas o cuando los datos provienen de fuentes externas (APIs, bases de datos), ya que el código puede compilar perfectamente pero fallar en producción ante un dato inesperado.
N° 18