Cómo el null safety de Dart me ayudó a mejorar mis proyectos

Migré una aplicación en ejecución y un paquete publicado a null safety, ¡y fue increíble!

Sobre el autor: Waleed Arshad es un tecnólogo móvil fundamental, un desarrollador multiplataforma apasionado y la primera persona de Pakistán en convertirse en Google Developer Expert para Flutter. Después de graduarse de FAST Karachi, ha estado trabajando en la industria por más de cinco años y actualmente trabaja en el equipo de Developer Experience para Flutter en Tendermint. También lidera la comunidad de Flutter de Pakistán.

Acercamiento de un diff de un editor de código mostrando una restricción de versión del SDK de Flutter cambiada para null safety.

Con el lanzamiento de Flutter 2, null safety estuvo disponible en el canal estable de Flutter. Este post habla sobre mis experiencias personales migrando mis apps y paquetes a null safety, además de crear apps null-safe desde cero. En resumen, ¡los resultados fueron increíbles!

Si no conoces la función de null safety de Flutter, consulta el anuncio de null safety. Si quieres entender completamente null safety, consulta la documentación de Dart sobre null safety.

Este artículo describe dos de mis experiencias con null safety:

  • Migrando una app y un paquete

  • Escribiendo código nuevo en un entorno null-safe

Migrando una app y un paquete

#

Cuando actualicé Flutter por primera vez a la versión 2.0 (la que soporta null safety) y actualicé la versión de mi Dart SDK a 2.12 en el archivo pubspec.yaml de mi app de Flutter, vi muchos errores. Inicialmente quería probar una migración manual a null safety (es decir, migrar sin la herramienta de migración), así que comencé a resolver los errores de null safety manualmente — añadiendo signos de interrogación y exclamación por todo mi código. Hice todo ese trabajo intencionalmente, ¡solo para entender todo el arduo trabajo realizado por el equipo de Flutter en la herramienta de migración de null safety para automatizar el proceso de cambio y actualización del código! Después de algo de experimentación, revertí todos los cambios manuales que había hecho a mano, y usé la magia de la herramienta para completar la migración de mi app.

La aplicación era una app experimental de estadísticas de COVID-19, y su código es completamente de código abierto. Puedes encontrarlo en GitHub.

Fue realmente genial ver todos los cambios de código que hizo la herramienta de migración en mi proyecto — cambios como añadir signos de interrogación en nullables y añadir signos de exclamación donde la herramienta de migración detectó que el valor nunca iba a ser null.

Diff de la herramienta de migración de null safety añadiendo marcadores nullable a campos de GlobalInfo, double y HomeCountry.

El siguiente es un ejemplo de la herramienta de migración añadiendo automáticamente signos de interrogación y exclamación. _homeCountry es una propiedad nullable de una clase llamada HomeCountry (que también es nullable). Por lo tanto, para proteger el acceso a una de las propiedades de _homeCountry, la herramienta añadió el operador de signo de interrogación.

Fragmento de código mostrando la herramienta de migración añadiendo automáticamente signos de interrogación a propiedades nullable.

Después de la migración, algunos problemas en el código se hicieron evidentes, lo cual es la mejor parte.

Fragmento de código revelando un problema expuesto después de migrar a null safety.

Uno de los problemas era que algunas strings nullable se estaban pasando como una lista a una función interna del plugin shared_preferences. Como estos valores eran nullable, la herramienta hizo que el tipo de toda la lista fuera <String?>[], lo cual empezó a dar un error porque la función aceptaba el tipo <String>[].

Error del analyzer sobre pasar strings nullable a una función que espera strings non-nullable.

Una solución simple a este problema era eliminar el signo de interrogación y hacer que el tipo de la lista coincidiera con el tipo del parámetro de la función. Cuando hice eso, el analyzer empezó a decir que un tipo nullable (String?) no puede asignarse a un tipo non-nullable (String).

Clase HomeCountry de Dart con campos String non-nullable y parámetros de constructor required.

Para resolver el problema, hice que cada propiedad de la clase HomeCountry fuera non-nullable y añadí la palabra clave required en el constructor. Eso significaba que ahora era necesario pasar los argumentos al inicializar HomeCountry. No tuve que cambiar la función setHomeCountry porque las variables que se pasaban a la lista ahora eran non-nullable.

Este cambio me impidió enviar por error un valor null a shared preferences en mi código, ¡lo cual fue una contribución muy valiosa de la función de null safety!

Otra cosa que encontró null safety fue un bug que podía causar un crashe en tiempo de ejecución. Mira el siguiente fragmento de código:

Advertencia del IDE sobre acceso a índice de lista porque una lista nullable podría ser null antes de la indexación.

Como list era una variable nullable, leer sus elementos basados en índice podía causar un crashe. Después de migrar a null safety, no pude compilar la app porque no había una verificación de null antes de leer los valores en esta lista.

Finalmente, añadí una verificación de null para hacer que el código compilara y para evitar que la app crasheara en este punto del código. ¡Es increíble cómo la migración me ayudó a encontrar un bug real!

Fragmento de código mostrando una verificación de null añadida para hacer válido el código setState.
El código para setState ahora es válido porque list no es null.

También me pasó migrar un paquete muy pequeño, que puedes encontrar en pub.dev, llamado progress_indicators. Me sorprendió ver cómo la herramienta de migración añadió palabras clave late en lugar de signos de interrogación cuando concluyó que esas variables se inicializaban antes de ser usadas.

Fragmento de código mostrando la herramienta de migración usando palabras clave late para variables inicializadas.

Escribiendo código nuevo en un entorno null-safe

#

Ahora que Flutter tiene null safety, crear apps nuevas es una mejor experiencia de desarrollo. Escribir código nuevo en un entorno null-safe también proporciona una mejor comprensión de los flujos de código, además de poder escribir código a prueba de crasheos. Ya no puedes crear código compilable con una clase como esta ahora:

class MyClass {
  String a;

  MyClass({this.a});
}

Este código causa un error en tiempo de compilación que dice que marques a como nullable, pongas una palabra clave required en el constructor, o añadas un inicializador. El código se asegura de que a nunca sea null. Entonces, dependiendo de tu caso de uso, podrías hacer esto:

class MyClass {
  String? a;

  MyClass({this.a});
}

O podrías hacer esto:

class MyClass {
  String a;

  MyClass({required this.a});
}

Ten en cuenta que no pones un signo de arroba (@) antes de la palabra clave required, a partir de Flutter 2 (y Dart 2.12).

O podrías mantener a como opcional, pero añadir un inicializador dándole un valor por defecto si no se pasa uno:

class MyClass {
  String a;

  MyClass({this.a = ''});
}

Además, ahora puedes crear variables nullable de tus propias clases creadas:

class MyClass {
  String? a;

  MyClass({this.a});
}

// somewhere in main code

MyClass? myClass;

Como myClass tiene un tipo nullable, tu compilador dará un error si escribes algo como esto:

print(myClass.a);

Aquí está el error:

La propiedad 'a' no puede ser accedida incondicionalmente porque el receptor puede ser 'null'.

Puedes arreglar ese error añadiendo un signo de interrogación:

print(hello?.a);

La función de null safety de Dart se asegura de que escribas código menos vulnerable y más seguro. Detectar errores relacionados con variables null durante el tiempo de compilación es una adición valiosa a la experiencia de desarrollo. El ejemplo anterior muestra cómo el compilador te impide compilar el código siempre que detecta la posibilidad de que una excepción de puntero null cause que la app crashee en tiempo de ejecución. Esto significa claramente que el compilador intenta escribir código null-safe tanto como sea posible (a menos que uses el operador ! para forzar el desempaquetado de todo).

En conclusión, el sound null safety de Dart es un gran punto de partida para construir apps más seguras, rápidas y confiables. La experiencia general de programación ahora está muy estructurada y más organizada. Recomiendo que migres tus apps antiguas de Dart a null safety para entender cómo funciona. ¡Quizás tengas la suerte de encontrar y arreglar algunos bugs en tu código antiguo!

¡Feliz programación! :)

Más de Dart