Sound Null Safety no Flutter
“I call it my billion-dollar mistake. It was the invention of the null reference in 1965.” — Tony Hoare
Esta frase é do inventor do Null, mas como é dito no video do Dev Fest LATAM sobre null safety no Flutter, talvez o único problema tenha sido não ter incluído nenhum tipo de null safety junto, afinal necessitamos de uma abstração que indique que algo não tem um valor ou é inválido em alguns casos.
O Sound Null Safety foi lançado na versão 2 do Flutter no dia 03 de Março de 2021 em conjunto com muitas outras novidades como as versões estáveis do Flutter web e desktop: https://developers.googleblog.com/2021/03/announcing-flutter-2.html
Hoje a forma mais fácil de testar é via DartPad, não é necessário ter o Flutter instalado, tudo roda no navegador e será possível replicar e acompanhar a maioria dos exemplos deste artigo apenas usando o DartPad: https://nullsafety.dartpad.dev
O DartPad ainda conta com alguns snippets para exemplificar e auxiliar no entendimento do null safety no Dart, como pode ser visto ao lado.
No caso deste artigo primeiro vamos entender a motivação e alguns conceitos importantes para poder seguir em frente com o código.
Por que Null Safety é importante?
Muitas pessoas se fazem essa pergunta e embora para alguns não pareça ter utilidade, o Null Safety ajuda a prevenir erros que só seriam percebidos em runtime e que se não forem identificados nos testes, vão com certeza aparecer em produção (lei de Murphy). O fato do programador agora receber estes erros antes da compilação graças ao Dart analyzer faz com que muitos erros sejam evitados. Segundo o time do Dart entre 80 e 90% das variáveis nunca assumem o valor null (Dev Fest LATAM Null safety in Dart, minuto: 3:35).
Mas e o tal do Sound Null Safety? O que é isso?
Segundo a documentação do Dart o Null Safety da linguagem é baseado em três princípios centrais:
- Non-nullable by default: A não ser que você explicitamente defina que um atributo pode ser null ela será considerada non-nullable. É explicado que essa decisão foi tomada após constatar com uma pesquisa que a maioria das APIs são non-nullable por padrão.
- Incrementally adoptable: É possível escolher o quê e quando migrar para null safety. Será possível migrar pacotes individualmente e existem ferramentas para ajudar na migração.
- Fully sound: O tipo de null safety que o dart adota é chamado de “sound”, a grande vantagem é que isto permite otimizações na compilação. Se o sistema de tipos definir que algo não pode ser nulo então esta definição será sempre válida. Uma vez que um projeto e suas dependências sejam migrados para null safety será possível aproveitar todos os benefícios, além de menos bugs também resultará em binários menores e execução mais rápida. O Sound Null Safety foi lançado na versão 2 do Flutter no dia 03 de Março de 2021.
Entendendo como funciona na prática, vamos ao código!
Como foi dito anteriormente é preciso declarar explicitamente que um atributo pode assumir o valor null, para fazer isso usa-se o caractere “?” logo depois da definição do tipo como pode ser visto no exemplo da figura 1 onde o Dart Analyzerjá informa ao programador que será necessário tratar o caso onde o atributo pode assumir o valor null.
A seguir temos a definição de dois atributos que não podem assumir valores nulos: peso e nome. Já o atributo apelido neste caso poderia assumir o valor null. Do ponto de vista de uma aplicação que registra o peso, nome e apelido de pessoas esta implementação faria sentido do ponto de vista que nem todo mundo tem um apelido e o atributo não fosse obrigatório.
String nome;
String? apelido;
double peso;
Se você tentou colocar o exemplo acima em uma classe no DartPad deve ter visto um erro como na figura a seguir:
Logo de cara o Dart Analyzer já está informando que as variáveis nome e peso precisam de um valor. Você talvez pense “Nossa tá faltando o construtor, deve ser isso”, em parte sim, um construtor comum resolveria nosso problema, mas no Flutter por exemplo é muito comum usar parâmetros nomeados nos construtores e é aí onde temos outro problema, parâmetros nomeados são opcionais:
Como resolver este problema? Uma das novas sintaxes introduzidas foi a palavra reservada required, que é usada para explicitar que aquele argumento será obrigatório e não deve ser null;
Além destas duas novas sintaxes existem mais duas que são o “late” e o sinal de exclamação “!” o late é utilizado quando é preciso declarar uma variável, mas temos certeza de que ela será inicializado depois embora o Dart não consiga provar isso. Vamos assumir agora por exemplo que seria necessário calcular a massa de uma pessoa, e o peso seria um atributo desta classe que depende do valor da massa:
Já a exclamação será usada para fazer o Dart explicitamente ignorar que um valor pode ser null neste caso é como se não houvesse Null Safety e por isto é melhor evitar usar “!” o máximo possível no código, como pode ser visto é possível até inserir o valor null em uma variável que não deveria assumir este valor e apenas uma warning foi gerada neste caso, porém em casos mais complexos nenhum aviso seria exibido e a responsabilidade é toda do programador em tratar os valores para garantir que nada de errado aconteça:
Para definir listas, mapas ou outros atributos com tipos dinâmicos, usa-se a sintaxe a seguir:
// Lista e elementos não podem assumir valor null
List<String> listaA;
// Lista pode ser null, mas seus elementos não
List<String>? listaB;
// Lista não pode ser null, mas seus elementos podem ser null
List<String?> listaC;
// Tanto a lista quanto seus elementos podem ser null
List<String?>? listaD;
Migrando seu código existente para null safety
Bom antes de tudo, espere até que o null safety entre em uma release estável para migrar o seu código existente, mas caso você queira testar com projetos pessoais menores, todo o processo será descrito a seguir.
O ideal seria que todas as suas dependências já tivessem sido migradas para null safety, ou pode ser necessário fazer alterações futuras no código, mas mesmo sem tudo migrado ainda conseguimos prosseguir.
- Atualizado o Flutter para a versão 2
flutter upgrade
flutter --version
Para checar o status das dependências, na pasta do projeto execute o comando a seguir:
dart pub outdated --mode=null-safety
Como pode ser visto na figura 10, no projeto testado ainda não havia uma versão null safe da data_connection_checker e será necessário “forçar” alguns passos por conta disso para poder ativar e testar null safety no projeto.
Com base na saída apresentada agora deve-se atualizar todas as dependências que possuem suporte pra null safety para suas respectivas versões no arquivo pubspec.yaml e depois rodar o comando: dart pub upgrade.
Para ativar o modo de null safety use o comando (caso todas as suas dependências tiverem sido migradas): dart migrate
Não é recomendado que você atualize antes de ter suas dependencias migradas, mas se assim como no meu projeto de exemplo ainda existirem dependencias sem suporte ao null safety e você desejar atualizar. Será necessário passar a flag “ — skip-import-check” o comando ficaria assim: dart migrate — skip-import-check
Se tudo correr bem um haverá um link ao final da execução levando para a interface da ferramenta de migração:
Como pode ser visto na Figura 12 no navegador será possível verificar todas as mudanças que foram feitas pela ferramenta também é possível editar caso não se concorde com algo. Ou adicionar “dicas” para a ferramenta de migração seguir numa segunda execução, para mais detalhes sobre isso consultar a documentação oficial: https://dart.dev/null-safety/migration-guide, ou o video sobre null safety do Dev Fest LATAM sobre o assunto: https://www.youtube.com/watch?v=b6YntQSKS7g Pronto quando não forem encontrados nenhum problema com o código migrado basta clicar em “APPLY MIGRATION” no canto superior direito e finalizar o processo no terminal.
Se você for olhar o código na sua IDE provavelmente vai encontrar um monte de erros e warnings, mas não entre em pânico está tudo certo, ainda é preciso ativar o null safety para que ele entre em vigor, o comando para realizar esta tarefa no projeto é: dart — enable-experiment-non-nullable ./lib/main.dart na raiz do seu projeto, note que no meu caso foi usado o arquivo main.dart dentro da pasta lib, mas a depender de como o seu projeto está estruturado isso pode ser diferente.
Com isso agora não devem mais haver erros no projeto provenientes da análise estática em conflito com o código null safe, se todas suas dependências já haviam sido migradas já deve ser possível rodar o projeto sem problemas, caso contrário é preciso desabilitar o sound null safety passando a flag: — no-sound-null-safety. Ou seja seu código foi escrito respeitando as novas sintaxes para ser null safe, mas até que todas as suas dependências sejam atualizadas também, não será possível ter toda a segurança e ganhos esperados ao compilar a aplicação.
Fontes:
- Sound null safety — https://dart.dev/null-safety
- Null safety in Flutter — https://flutter.dev/docs/null-safety
- Announcing sound null safety — https://medium.com/dartlang/announcing-sound-null-safety-defd2216a6f3
- Announcing Dart null safety beta — https://medium.com/dartlang/announcing-dart-null-safety-beta-87610fee6730
- Dev Fest LATAM — Null safety in Dart — https://www.youtube.com/watch?v=b6YntQSKS7g
- Migrating to null safety — https://dart.dev/null-safety/migration-guide
- Wikipedia | Tony Hoare — https://en.wikipedia.org/wiki/Tony_Hoare
- Announcing Flutter 2 — https://developers.googleblog.com/2021/03/announcing-flutter-2.html