It was a chill Thursday night. I was working on a profile update feature for one of my Flutter apps. I had just refactored a bunch of code and felt pretty confident.
After all, Dart has null safety now what could go wrong?
The app launched, everything looked good. But then… Crash.
Not a red screen. Not a console warning. A full-on, “Unhandled exception: Null check operator used on a null value.”
I was confused.
“Wait, I’m using null safety. Isn’t this stuff supposed to be impossible now?”
I had sprinkled a few !
operators here and there (you know, just to keep the compiler happy). But that one exclamation mark brought the whole thing down.
That was my wake-up call.
And that night, I went deep into understanding Dart’s null safety. In this post, I’ll share:
- What I did wrong
- How Dart null safety really works
- What tools Dart gives you (
?
,!
,late
,required
) - Best practices to keep your app safe, clean, and crash-free
What Is Null Safety, Really?
Null safety in Dart means you have to explicitly declare when a variable can be null.
Before null safety, any variable could potentially be null, leading to tons of runtime crashes.
Now, you write:
String name = 'Md. Al-Amin'; // non-nullable
String? nickname; // nullable
Dart forces you to think about nulls. It doesn’t let you do:
print(nickname.length); // ❌ compile error
You need to check if it’s null:
if (nickname != null) {
print(nickname.length);
}
Or use !
to say “I promise this isn’t null” (but use it carefully… more on that soon).
What I Did Wrong: The Dangerous !
Operator
Back to my bug…
I had this code:
String? userToken;
// later...
apiClient.setToken(userToken!);
In my head, I was sure userToken
was initialized before calling setToken
. But in reality, it wasn’t.
That !
is called the null assertion operator, and it basically says:
“Trust me, this is not null don’t check, just run.”
But Dart doesn’t protect you here. If userToken
is null, you crash.
And that’s exactly what happened.
Let’s Break Down the Null Safety Tools
?
→ Nullable Variable
String? email;
This means email
can be either a String
or null
.
Use ?
when:
- You’re receiving data from external sources (APIs, storage, etc.)
- A variable truly might not be set (optional fields)
!
→ Null Assertion
String? email;
print(email!); // You better be 100% sure it's not null
Use this only when you’re absolutely certain a value can never be null at that point.
When it’s okay:
if (email != null) {
print(email!); // you're safe here
}
When it’s dangerous:
print(email!.length); // if email is null = crash!
Tip: Avoid using !
unless you’ve done a prior null check.
late
→ Delayed Initialization
What if a variable can’t be set immediately, but you’re sure it’ll be set before use?
late String token;
void init() {
token = fetchToken();
}
void makeRequest() {
print(token); // Dart assumes it's set before use
}
If you forget to assign it before access Dart throws an error at runtime.
Use late
when:
- You want to avoid
null
, but initialize later (e.g., ininitState
) - You’re working with dependency injection
Don’t use it as a lazy shortcut for avoiding ?
or !
.
required
→ Non-nullable named parameters
Before null safety:
void createUser(String name, String email);
After null safety:
void createUser({required String name, required String email});
If the caller doesn’t pass name
or email
, Dart throws a compile-time error.
Use required
for:
- Named parameters that are essential
- Enforcing contracts in your API or widget constructors
Real-World Use Case: User Profile Model
Let’s look at a simple User
model that could crash if nulls aren’t handled well:
class User {
final String name;
final String? email;
final String? phone;
User({
required this.name,
this.email,
this.phone,
});
}
Now, when rendering UI:
Text(user.email ?? 'No email available'),
That’s safe. No !
needed. No risk of crash.
Best Practices for Null Safety in Dart
Here’s what I learned and now live by:
Do This
- Use
?
when data may be null - Use
required
for essential fields - Check nulls before using values
- Use defaults like
??
- Write unit tests for null cases
Avoid This
- Making everything
late
to avoid?
- Forcing values with
!
everywhere - Trusting external data blindly
- Assuming things are initialized
- Ignoring edge cases in models
Extra Tip: Null-Aware Operators in Dart
Dart gives you some powerful operators:
??
→ Default value if null
print(user.email ?? 'No email');
??=
→ Assign default if null
email ??= 'unknown@example.com';
?.
→ Safe access
print(user.profile?.bio); // If profile is null, won’t crash
Final Thoughts
Dart’s null safety is amazing, but it only works if we understand how to use it.
Null safety doesn’t mean “no more crashes.”
It means: “You now have the power to prevent them, but you have to think.”
My Advice to You
- Avoid the
!
unless you’re absolutely sure - Use
?
and null-aware operators liberally they’re your friends - Don’t go overboard with
late
— it’s powerful, but sharp - And always, always test your edge cases
If Dart asks you to handle nulls, it’s not being annoying its saving future you from a nightmare.
I’d love to hear from you:
Have you ever been betrayed by a !
? Or misused late
and got runtime surprises?
Share your experience and let’s help each other write safer Dart code.
Pattern matching can also help. It's easy to rule out nulls from almost everywhere that way.