DMarlon Studio

Lección 11 · 11 de 20

Patrones y clases selladas · Dart

Modele los estados de una pantalla con clases sealed en Dart 3 y trátelos con un switch exhaustivo, patrones de objeto, guardas when y if-case.

Dart · Patrones y clases selladas en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una clase `sealed` (sellada) solo puede tener subtipos en su propio archivo. Es abstracta y, a cambio, el compilador conoce la lista completa de sus subtipos: Loading, Loaded y Failed. Es la forma habitual de modelar los estados de una pantalla en Flutter: cargando, con datos o con error.

render usa una expresión `switch` con patrones de objeto: Loaded(:final items) comprueba que el estado es un Loaded y extrae su campo items en una variable. Los patrones pueden afinar más: Loaded(items: []) solo encaja con la lista vacía, Failed(code: 401) con ese código concreto, y la guarda when items.length == 1 añade una condición al caso.

El orden importa: los casos se prueban de arriba abajo y gana el primero que encaja, por eso el general Loaded(:final items) va después de los particulares. Y como la clase está sellada, el switch es exhaustivo: no necesita default, y si se borra el caso Loading() o se añade un estado nuevo, el programa no compila hasta cubrirlo.

Los patrones no son solo para clases. La última línea usa if (json case ...) con un patrón de mapa y otro de lista: comprueba que hay una clave user de tipo String y una lista roles cuyo primer elemento es texto (... acepta el resto), y de paso guarda los valores en name y role. Es una forma segura de leer JSON sin una cadena de comprobaciones y conversiones.

Código · main.dart

// sealed: every subtype is declared in this file, so the compiler// knows the complete list and can check that a switch covers it.sealed class LoadState {}class Loading extends LoadState {}class Loaded extends LoadState {  final List<String> items;  Loaded(this.items);}class Failed extends LoadState {  final int code;  final String message;  Failed(this.code, this.message);}// Exhaustive switch: no default needed; forget a case and it won't compile.String render(LoadState state) => switch (state) {      Loading() => 'Showing spinner...',      Loaded(items: []) => 'Nothing here yet',      Loaded(:final items) when items.length == 1 => 'One item: ${items[0]}',      Loaded(:final items) => '${items.length} items: ${items.join(', ')}',      Failed(code: 401) => 'Session expired, please log in',      Failed(:final code, :final message) => 'Error $code: $message',    };void main() {  final states = [Loading(), Loaded([]), Loaded(['Milk']), Loaded(['Tea', 'Rice']),      Failed(401, 'Unauthorized'), Failed(503, 'Server busy')];  for (final state in states) print(render(state));  // Patterns also check and unpack a map, such as decoded JSON.  final Object json = {'user': 'carmen', 'roles': ['admin', 'editor']};  if (json case {'user': String name, 'roles': [String role, ...]}) print('$name is $role');}

Salida · dart run main.dart

Showing spinner...
Nothing here yet
One item: Milk
2 items: Tea, Rice
Session expired, please log in
Error 503: Server busy
carmen is admin

Para recordar

  • sealed fija la lista de subtipos en el mismo archivo.
  • Un switch sobre una clase sellada es exhaustivo: no hace falta default.
  • Clase(:final campo) comprueba el tipo y extrae el campo.
  • when añade una condición a un caso.
  • if (valor case patrón) valida y desestructura a la vez.

Ejercicio

Añada un estado Offline a la clase sellada, observe el error del compilador en render y añada el caso que escriba No connection.