Flutter - Null Safety
A partir do Dart 2.12, temos a possibilidade de usar o Null Safety para facilitar nossas aplicações, dificultando erros de valores nulos. Aprenderemos a identificar erros de valores nulos, aplicar no nosso projeto do ByteBank, atualizar o Dart para a nova versão e migrar um projeto inteiro! Assim podemos criar códigos complexos com mais facilidade!
Erros de valor Null
Existem infinitos erros que podem acontecer por conta de um valor nulo:
- Você pode estar esperando um dado do seu back-end, mas ele não existe ainda…
- Você pode criar uma lista que muda de tamanho de acordo com a quantidade de produtos…
Vamos dar uma olhada no nosso antigo conhecido: o ByteBank. Nele, temos uma lista de transferências que depende da quantidade de transferências disponíveis no nosso servidor API.
return ListView.builder(
itemBuilder: (context, index) {
final Transaction transaction = transactions[index];
return Card(
child: ListTile(
leading: Icon(Icons.monetization_on),
title: Text(
transaction.value.toString(),
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
),
subtitle: Text(
transaction.contact.accountNumber.toString(),
style: TextStyle(
fontSize: 16.0,
),
),
),
);
},
itemCount: transactions.length, /// não pode ser nulo
);
Mas e se não tivéssemos nenhuma transferência?
Nesse caso, nossas transferências teriam o valor null
e a ListView não poderia ser buildada, causando a famosa Tela Vermelha.
Para resolver isso, no passado, usávamos uma condição que ignorava o valor nulo.
if(snapshot.hasData){ // condição inicial para impedir o nulo
final List<Transaction> transactions = snapshot.data;
if(transactions.isNotEmpty){ // segunda condição para impedir o nulo
return ListView.builder(...);
}
}
return CenteredMessage('No Transactions Found', icon: Icons.warning); // caso seja nulo, buildar essa mensagem
break;
}
return CenteredMessage('Unknown Error'); // caso seja nulo, buildar essa mensagem
Aplicação do Null Safety
Agora, as coisas funcionam de uma forma levemente diferente. Por padrão, toda variável que criamos não pode receber valor nulo.
Mas e se quisermos ter um valor nulo? Existem alguns momentos em que precisamos lidar com valores nulos, então, como podemos garantir que nossa variável vai receber o valor nulo? Para esses casos, basta adicionar o sinal ? e, em seguida, o tipo da variável.
int? cafe = null;
Existem casos extremos em que precisamos ficar alterando a propriedade da nossa variável. Imagina o seguinte: você está fazendo uma função, e ela não pode receber um valor nulo, mas, por algum motivo, um dos atributos dela veio nulo. O que podemos fazer para garantir que esse valor vai ser transformado em não nulo?
class Cafe {
String _temperatura; // criei a Temperatura
void esquentar() { _temperatura = 'quente'; } // Defino a Temperatura para quente
void esfriar() { _temperatura = 'gelado'; }
String servir() => _temperatura + ' cafe'; // Servir o Cafe com a Temperatura
}
main() {
var cafe = Cafe(); // Fazer o Cafe
cafe.esquentar(); // Esquentar o Cafe
cafe.servir(); // Servir o Cafe sem esperar que ele já esteja quente
}
Note que, no momento em que estamos servindo, estamos esquentando! Nossa função não espera o _temperatura
ser = ‘quente’
antes de servir… Ou seja, se for rápida o suficiente, ela pode entregar o café com a _temperatura = ‘null’
Para esses casos, basta adicionar o sinal ! no tipo da variável.
class Cafe {
String? _temperatura; // Criar a Temperatura que não pode ser nula
void esquentar() { _temperatura = 'quente'; } // Definir a Temperatura para quente
void esfriar() { _temperatura = 'gelado'; }
String servir() => _temperatura! + ' cafe'; // Servir o Cafe com a Temperatura que pode ser nula.
}
Mas já dá pra notar que isso não é uma boa prática, né? Afinal, ficar colocando ? e ! a torto e a direito no código pode confundir qualquer pessoa!
Existe um novo modificador chamado late
, que permite que o valor inicialmente seja nulo, mas, quando for utilizado em alguma função ou classe, ele vai ser considerado não nulo.
class Cafe {
late String _temperatura; // criei a Temperatura
void esquentar() { _temperatura = 'quente'; } // Definir a Temperatura para quente
void esfriar() { _temperatura = 'gelado'; }
String servir() => _temperatura + ' cafe'; // Servir o Cafe com a Temperatura só depois que a _temperatura for diferente de nula.
}
O que acontece é que o late
pede para a _temperatura
ser usada na função somente quando ela já tiver deixado de ser null
.
Migração
Vamos agora migrar um aplicativo para utilizar o Null Safety. Antes de tudo, temos de ter certeza de que tudo está nos conformes e para isso vamos usar o comando flutter doctor
e dart --version
no terminal para ver se temos tudo atualizado!
Para que tudo funcione, precisamos do Flutter na versão mínima 2.0.0 e o Dart na versão mínima 2.12.0. Caso você precise atualizar algum deles, utilize o comando flutter upgrade
e dart upgrade
, respectivamente.
Agora temos de atualizar nosso projeto!
Existem dois jeitos de migrar seu projeto. A grande maioria de nós vai tentar migrar um projeto do jeito manual:
- Ir manualmente no
pubspec.yaml
e atualizar os pacotes um por um, “na mão”, depois substituir as depreciações uma a uma até que o projeto não tenha nenhum erro ou warning.
Devo alertar você que o jeito manual é muito trabalhoso e pode te dar algumas noites sem sono e alguns cabelos brancos. Então, vou te convencer a fazer de um outro jeito!
Primeiro passo: Verificar
Saber quando devemos atualizar nosso projeto é essencial! Para isso, verifique quais são os pacotes que você usa no seu projeto e se eles já têm a versão que aceite o Dart 2.12 (Null Safety).
Atente-se aos pacotes que dependem de outros pacotes! Okay, mas como vou saber se meus pacotes têm suporte pro Null Safety? Simples, para isso basta usarmos o comando no terminal:
dart pub outdated --mode=null-safety
Esse comando vai analisar cada um dos seus pacotes e confirmar se eles já aceitam a Migração! (Bem melhor do que sair procurando cada um no Google, não é?)
Caso seus pacotes tenham suporte para o Null Safety, basta usar dois comandos simples para atualizá-los!
dart pub upgrade --null-safety
e
dart pub get
Segundo passo: Migrar
Agora nós temos o terreno pronto, então, vamos começar a migração! A primeira coisa a se fazer é lançar o comando:
dart migrate
Em seguida, ele vai te mandar um link que você deve acessar pelo seu navegador:
View the migration suggestions by visiting: http://127.0.0.1:60278/Users/you/project/mypkg.console-simple?authToken=Xfz0jvpyeMI%3D
Já no navegador, você vai ver uma interface linda com o seu código atualizado!
Calma, seu código não foi alterado ainda! Essa interface está apenas sugerindo as alterações. Antes de aplicar as alterações, vamos conferir se todos os valores que devem ser nulos estão especificados e se os valores não nulos estão corretos.
Caso você precise fazer uma alteração vá em Edit Details
e selecione o hint pertinente (?
ou !
). Uma vez que você conferiu todo o código e, estando tudo certo, clique em Apply migration
. Pronto! Seu projeto está todo atualizado para o Null Safety!
Incrível, não é? Muito mais fácil do que ir manualmente em cada pacote e verificar todas as variáveis no projeto a fim de encontrar algum que aceite o valor nulo, não acha?
Conclusão
Aprendemos bastante sobre o Null Safety e como ele funciona. Descobrimos a sua importância e entendemos as diferenças entre ?
, !
e o late
.
Vimos como atualizar o Dart e como ter certeza se podemos migrar o nosso projeto para aceitar o Null Safety. Utilizamos uma nova ferramenta que faz alterações inteligentes no nosso código – tudo isso de um jeito leve e rápido. Por fim, percebemos que é muito melhor usar ferramentas que fazem o trabalho pesado por nós, em vez de atualizar o código manualmente.