Sound Null Safety no Flutter

Thiago Fontes
Popcodemobile
Published in
8 min readMay 6, 2021

“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

Figura 1: Exemplo de erro relacionado o atributo que pode assumir valor Null
Figura 2: DartPad snippets

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:

Figura 3: Erro ao definir variáveis

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:

Figura 4: Erro ao se definir construtor com parâmetros nomeados

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;

Figura 5: Uso do required com um construtor de parâmetros nomeados

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:

Figura 6: Erro ao tentar inicializar o peso no construtor.
Figura 7: Usando late para indicar que variável será inicializado posteriormente.

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:

Figura 8: Inserindo null no peso da Pessoa

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
Figura 9: Exemplo de status das dependências de um projeto

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:

Figura 10: Exemplo de link produzido após executar a ferramenta de migração
FIgura 11: Interface web 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:

--

--