I Trusted Dart’s Null Safety… and It Still Crashed My App

Md. Al - AminMd. Al - Amin
5 min read

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., in initState)

  • 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.

0
Subscribe to my newsletter

Read articles from Md. Al - Amin directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Md. Al - Amin
Md. Al - Amin

Experienced Android Developer with a demonstrated history of working for the IT industry. Skilled in JAVA, Dart, Flutter, and Teamwork. Strong Application Development professional with a Bachelor's degree focused in Computer Science & Engineering from Daffodil International University-DIU.