Flutter Developers: The Dangerous Shortcut You’re Probably Using
Teqani Blogs
Writer at Teqani
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.
All blogs are certified by our company and reviewed by our specialists
Issue Number: #2f41d6bb-3bcf-4fb2-9404-8d13b58694bc