Solução: Null check operator used on a null value no Flutter
O operador ! encontrou um null em runtime. Substitua-o por ?. e ?? para um valor padrão, ou proteja com uma verificação de null explícita, em vez de afirmar um valor que não estava lá.
Este site usa Walter, um agente de IA, para pesquisa, redação e manutenção. Processo editorial e de verificação
Você escreveu something! e something era null quando a linha rodou. O operador de verificação de null (bang) promete ao compilador “isto nunca é null”, e o Dart faz cumprir essa promessa em runtime lançando o erro no instante em que ela é quebrada. A solução quase sempre é parar de afirmar e começar a tratar: use ?. para curto-circuitar, ?? para fornecer um valor padrão, ou uma proteção if (x != null) que deixa o compilador estreitar o tipo por você. Esta página usa Flutter 3.44 (estável, maio de 2026) e Dart 3.x.
O erro em contexto
Quando o operador bang encontra um null, você recebe um TypeError com esta mensagem exata:
Unhandled Exception: Null check operator used on a null value
Na camada de widgets ele geralmente aparece envolvido pelo framework, que é onde a maioria das pessoas de fato o vê:
======== Exception caught by widgets library =======================================================
The following _TypeError was thrown building ProfilePage(dirty):
Null check operator used on a null value
The relevant error-causing widget was:
ProfilePage ProfilePage:file:///lib/profile_page.dart:18:12
A classe é _TypeError (um subtipo de TypeError), a mesma família que o Dart usa para casts que falham. Essa é a pista: o operador bang é um cast. Ele converte T? para T, e como qualquer cast pode falhar em runtime.
Por que isso acontece: o operador bang é um cast verificado
Em null safety sólida, String? e String são tipos diferentes. O ! posfixo é uma forma abreviada que, nas palavras da documentação do Dart, “pega a expressão à esquerda e a converte para o seu tipo não anulável subjacente”. Um cast de um tipo anulável para um não anulável não pode ser provado como seguro em tempo de compilação, então o compilador insere uma verificação em runtime. Se o valor for null quando a verificação rodar, você recebe Null check operator used on a null value.
Então isso nunca é um bug do compilador nem do framework. É um valor sendo null em um momento em que o seu código jurou que ele não seria. O trabalho é encontrar o valor e decidir o que deve acontecer quando ele estiver genuinamente ausente, em vez de encobri-lo com outro !.
Reprodução mínima
A versão mais simples é uma única variável anulável à qual ainda não foi atribuído nada:
// Flutter 3.44, Dart 3.x
String? name; // nullable, defaults to null
void main() {
print(name!.length); // throws: Null check operator used on a null value
}
Em código Flutter real, o formato mais comum são dados que ainda não carregaram. O campo é null até uma chamada de rede preenchê-lo, mas build roda imediatamente e o desreferencia:
// Flutter 3.44, Dart 3.x -- crashes on first build
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
User? _user; // null until the fetch returns
@override
void initState() {
super.initState();
_loadUser(); // async, completes some frames later
}
Future<void> _loadUser() async {
final u = await api.fetchUser();
setState(() => _user = u);
}
@override
Widget build(BuildContext context) {
return Text(_user!.name); // <-- _user is null on the first build
}
}
build é chamado antes de _loadUser resolver, então _user ainda é null no primeiro frame e _user! lança o erro.
A solução, em ordem de preferência
A solução correta depende de null ser um estado legítimo (dados ainda carregando, campo opcional, chave ausente) ou um bug (você esperava um valor e a ausência dele significa que algo mais acima está quebrado). Na maioria das vezes é o primeiro caso, e o framework te dá ferramentas idiomáticas para isso.
1. Forneça um valor padrão com ??
Se existe um valor de fallback sensato, o operador de coalescência de null é a solução correta mais curta. Ele retorna o lado direito quando o esquerdo é null:
// Flutter 3.44, Dart 3.x
Text(_user?.name ?? 'Loading...');
_user?.name é null quando _user é null (o ?. curto-circuita toda a cadeia), e ?? substitui o marcador. Sem lançamento de erro, e a interface mostra algo útil enquanto os dados carregam.
2. Ramifique no estado de carregamento explicitamente
Quando não há um bom valor padrão, renderize widgets diferentes para o estado carregado e o ainda-não-carregado. Uma verificação if (x != null) promove a variável local para não anulável dentro do ramo, então você não precisa de ! algum:
// Flutter 3.44, Dart 3.x
@override
Widget build(BuildContext context) {
final user = _user; // copy to a local for promotion
if (user == null) {
return const Center(child: CircularProgressIndicator());
}
return Text(user.name); // user is User here, not User?
}
Copie o campo para uma variável local primeiro. O Dart só promove variáveis locais, não campos de instância, porque outro método (ou outro isolate) poderia mutar um campo entre a verificação e o uso. A variável local é a parte que sustenta esse padrão.
3. Deixe o FutureBuilder cuidar do estado null
Se o valor vem de uma única chamada assíncrona, não improvise o flag na mão. O FutureBuilder modela carregamento, erro e dados como um único objeto, e você só lê data depois de confirmar que ele está presente:
// Flutter 3.44, Dart 3.x
FutureBuilder<User>(
future: _userFuture, // created once, not in build -- see below
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Failed: ${snapshot.error}');
}
return Text(snapshot.data!.name); // safe: hasData is implied here
},
);
O snapshot.data! aqui é legítimo porque você já provou que o future completou sem erro. Uma ressalva que pega muita gente: crie o future uma única vez e armazene-o, nunca inline dentro de build, ou cada reconstrução inicia um novo fetch. Esse é um problema próprio, coberto em por que o FutureBuilder fica recriando o seu Future.
4. Use late somente quando a inicialização realmente precede a primeira leitura
Se um valor é atribuído exatamente uma vez, antes de qualquer coisa lê-lo, late remove a nulabilidade sem o bang. Mas isso é uma troca, não um ganho grátis: um campo late lido antes da atribuição lança LateInitializationError, um crash diferente e possivelmente pior, porque é fácil supor que late tornou o valor seguro. Recorra a ele apenas quando a ordem é garantida, por exemplo um valor definido em initState e lido em build:
// Flutter 3.44, Dart 3.x
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this); // assigned before any build
}
Se a ordem não é garantida, mantenha o campo anulável e proteja-o. O detalhamento completo de quando late ajuda e quando atrapalha está em como resolver o LateInitializationError no Flutter.
Os suspeitos de sempre além de um flag de carregamento
O erro se disfarça de várias formas. Estas são as de maior frequência, cada uma com a mesma causa subjacente e o mesmo formato de solução.
GlobalKey.currentState! antes de o widget estar montado. Chamar _formKey.currentState!.validate() quando o Form não está na árvore (ou ainda não foi construído) lança o erro, porque currentState é null até o widget se anexar. Use ?.:
// Flutter 3.44, Dart 3.x
if (_formKey.currentState?.validate() ?? false) {
// form is valid and present
}
Argumentos de rota que não foram passados. ModalRoute.of(context)!.settings.arguments as Args assume tanto que existe uma rota quanto que argumentos foram fornecidos. Se você empurra a rota sem argumentos, arguments é null e o as posterior ou um ! seguinte explode. Leia de forma defensiva:
// Flutter 3.44, Dart 3.x
final args = ModalRoute.of(context)?.settings.arguments as Args?;
if (args == null) return const ErrorScreen('Missing arguments');
Acesso a Map e JSON com [key]!. Uma busca em um map retorna null para uma chave ausente, e json['email']! lança o erro no momento em que o campo está ausente ou a API o renomeou. Decodifique através de um modelo com nulabilidade explícita ou atribua um valor padrão a cada campo:
// Flutter 3.44, Dart 3.x
final email = (json['email'] as String?) ?? '';
firstWhere com um bang sobre o resultado. Às vezes as pessoas escrevem list.firstWhere((e) => e.id == id, orElse: () => null)! para “encontrar ou estourar”. Isso é exatamente uma suposição não verificada. Prefira firstWhereOrNull do package:collection e trate o caso vazio:
// Flutter 3.44, Dart 3.x
final match = list.firstWhereOrNull((e) => e.id == id);
if (match == null) { /* handle not found */ }
Variantes que isto não é, e para onde ir em vez disso
O tráfego de busca para esse erro muitas vezes pertence a uma página vizinha. Três parecidos:
LateInitializationError: Field '_x' has not been initialized é um irmão, não o mesmo erro. Ele vem de ler uma variável late antes da atribuição, não de um ! sobre um anulável. Se o seu stack trace diz LateInitializationError, a solução está na página do LateInitializationError, não aqui.
Uma verificação de null que só falha depois de navegar, e só em release, costuma ser um sintoma de contexto morto. A busca sobre um contexto desativado retorna null em builds de release (o assert que o detecta é só de debug), e esse null então dispara um ! em algum ponto mais abaixo. Se o crash se correlaciona com um await seguido de um uso do contexto, leia usar BuildContext com segurança depois de um await, porque o erro real está acima do bang.
Um TextEditingController ou outro controlador usado depois de dispose também pode alimentar um null para uma afirmação posterior. Se o controlador é a fonte, como resolver o erro de controlador descartado aborda o ciclo de vida diretamente.
O lint que pega isso antes do runtime
O Dart não pode avisar sobre cada ! que possa falhar, porque esse é justamente o sentido do operador: você está sobrepondo o analisador. Mas ele pode sinalizar os que consegue provar que são inúteis. A regra unnecessary_non_null_assertion, ativada por padrão no flutter_lints, dispara quando você aplica um bang a um valor que o analisador já sabe ser não nulo, o que normalmente significa que o seu modelo mental e o sistema de tipos não concordam:
# analysis_options.yaml -- on by default via flutter_lints
include: package:flutter_lints/flutter.yaml
A disciplina mais ampla é tratar cada ! que você digita como uma afirmação que você precisa defender. Se você não consegue apontar a linha que garante que o valor é não nulo naquele caminho, você não tem um !, você tem um Null check operator used on a null value latente. Modelar os dados assíncronos como estados explícitos de carregamento e erro, como em estados de carregamento e erro com AsyncValue, elimina a maioria dessas afirmações por completo, porque o framework entrega o valor só no ramo onde ele existe.
O hábito que aposenta o erro
O operador bang é uma promessa ao compilador, quitada em runtime, e Null check operator used on a null value é o recibo de uma promessa quebrada. Sempre que você for tentado a escrever !, pergunte-se o que deve acontecer quando o valor for realmente null: um marcador (??), um widget diferente (if (x != null)), ou um erro real que você levanta de propósito. Escolha uma dessas opções e o crash nunca chega a um usuário. Reserve ! para o caso raro em que null seria uma violação genuína de uma invariante, e mesmo assim, prefira lançar um StateError com uma mensagem que explique o que deu errado a um bang pelado que diz apenas “isto era null”.
Fontes
- Understanding null safety, documentação do Dart, que define o operador
!como um cast verificado para o tipo não anulável e explica por que a verificação precisa rodar em runtime. - Null safety unsound migration and the
!operator, documentação da linguagem Dart. - unnecessary_non_null_assertion lint rule, regras do linter do Dart.
- firstWhereOrNull, documentação da API do
package:collection.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.