Flutter Developers: The Dangerous Shortcut You’re Probably Using

Flutter Developers: The Dangerous Shortcut You’re Probably Using

TB

Teqani Blogs

Writer at Teqani

May 4, 20255 min read

The ! operator in Dart brings hype in chat, but in Dart code, it brings crashes. Dart’s null safety was a game-changer, helping catch null errors at compile time. However, the non-null assertion operator (!) can lead to runtime crashes if misused. This article explores why using ! is risky, better options that exist, and how experienced Flutter developers handle these scenarios.

Why Using ! is Risky

Using the ! operator tells Dart, "I'm 100% sure this value won't be null." But bugs often arise from overconfidence. If the value is actually null, your app crashes. It feels like a fix until your app crashes or you encounter a grey screen in production.

Common Flutter Mistake: Using Bang (!) in Model Parsing

Consider JSON parsing. Online converters often blindly use the ! operator when generating model classes. If a key is missing or null, it can lead to unexpected runtime crashes that are easy to overlook during development.

Wrong Way (Using ! Operator):

class User {
 final String name;
 User({required this.name});
 factory User.fromJson(Map<String, dynamic> json) {
 return User(name: json['name']!); // Forcefully unwrapping nullable value
 }
}
void main() {
 final userJson = {'name': null};
 final user = User.fromJson(userJson); // Runtime crash if 'name' is null
}

In this code, we use the ! operator to assert that the name key exists and isn't null. But if the key is missing entirely or value is null, the app crashes. This is a risky shortcut that ignores potential issues.

The Right Way: Handling Missing Keys with ??

class User {
 final String name;
 User({required this.name});
 factory User.fromJson(Map<String, dynamic> json) {
 return User(name: json['name'] ?? 'Unknown'); // Use default value if null
 }
}
void main() {
 final userJson = {'name': null};
 final user = User.fromJson(userJson); // No crash, 'Unknown' is used as default
 print(user.name); // Output: Unknown
}

In Widgets

When passing user data into a widget:

class ProfilePage extends StatelessWidget {
 final User? user;
 ProfilePage({this.user});
 @override
 Widget build(BuildContext context) {
 return Column(
 children: [
 Text(user!.name), // ❌ Bad: Will crash if user is null
 Text(user!.email),
 ],
 );
 }
}

If the user data is still loading, or was never passed in, and it's null, this widget could crash instantly. How can we prevent this and ensure a smooth user experience even when data is missing or delayed?

  • Use a Null Check Before Using the Variable
@override
Widget build(BuildContext context) {
 if (user == null) {
 return Center(child: Text('Loading...'));
 }
 return Column(
 children: [
 Text(user.name),
 Text(user.email),
 ],
 );
}

Why this is better:

  • Prevents crashes.
  • Shows fallback UI when data isn’t ready.
  • Use Null-Aware Operators
@override
Widget build(BuildContext context) {
 return Column(
 children: [
 Text(user?.name ?? 'Guest'),
 Text(user?.email ?? 'Not available'),
 ],
 );
}

What’s happening here:

  • user?.name returns null if user is null
  • ?? provides a fallback value

The Secret Sauce: Ban the ! Operator Like a Pro

Here’s a quick hack to catch every use of the non-null assertion operator (!) and turn it into a compile-time error:

analyzer:
 plugins:
 - custom_lint
errors:
 undefined_lint: ignore
 avoid_non_null_assertion_operator: error
linter:
 rules:
 avoid_non_null_assertion_operator: error

You need a dev dependencies for this avoid_non_null_assertion_operator.

Now every time you use ! to force a nullable value, they’ll get a nice red error instead of a surprise runtime crash.

Let’s be honest—we’ve all been there.

TB

Teqani Blogs

Verified
Writer at Teqani

Senior Software Engineer with 10 years of experience

May 4, 2025
Teqani Certified

All blogs are certified by our company and reviewed by our specialists
Issue Number: #2f41d6bb-3bcf-4fb2-9404-8d13b58694bc