Topzee logo
Open menu
Mobile Development

Dart Null Safety Errors: How to Avoid the Ones That Break Your Build

A clear breakdown of Dart null safety, the errors it prevents, and how to fix the ones that still trip up new developers.

Topzee4 min read
Abstract Dart null safety cover with guarded data shapes and Topzee blue geometry.

Dart null safety errors are frustrating at first because they force you to be explicit. Once the habit clicks, they prevent a whole category of runtime crashes. Instead of pretending every value exists, Dart asks you to model uncertainty directly.

That is a good thing in Flutter apps, where data often comes from forms, APIs, local storage, route arguments, or platform services.

Dart Null Safety Errors: The Core Idea

Null safety means a variable cannot be null unless its type allows it.

String name = 'Topzee';
String? nickname = null;

String means the value must always be a string. String? means the value can be a string or null.

If you try this, Dart stops you:

String name = null; // Compile error

That feels strict, but it keeps a mistake from reaching users.

Nullable vs Non-Nullable Types

Use a non-nullable type when the value is required for the object to make sense.

class User {
  const User({
    required this.id,
    required this.email,
    this.avatarUrl,
  });

  final String id;
  final String email;
  final String? avatarUrl;
}

In this model, a user must have an id and email, but may not have an avatar.

That small distinction affects the UI:

if (user.avatarUrl != null) {
  return Image.network(user.avatarUrl!);
}

return const CircleAvatar(child: Icon(Icons.person));

The ! is safe only because the code checked for null first. In many cases, you can avoid ! entirely:

final avatar = user.avatarUrl;

if (avatar == null) {
  return const CircleAvatar(child: Icon(Icons.person));
}

return Image.network(avatar);

“Null Check Operator Used on a Null Value”

This error means you used ! on a value that was actually null.

String? token;
print(token!); // Runtime crash

The fix is not “add more exclamation marks.” The fix is to decide what should happen when the value is missing.

if (token == null) {
  throw StateError('Token is required before making this request');
}

print(token);

Or provide a fallback:

final displayName = user.name ?? 'Guest';

Use ! only when you can prove the value exists.

Late Initialization Errors

late tells Dart: this non-null value will be assigned before it is used.

late final String sessionId;

If you read it before assignment, you get a runtime error.

late is useful for framework-driven values or setup that cannot happen in the constructor, but it can become a hiding place for unclear ownership.

Prefer constructor requirements when possible:

class Session {
  const Session({required this.id});

  final String id;
}

Use late when lifecycle demands it, not because you want to avoid thinking about nullability.

Safe Patterns to Reach For

Use ?? for fallbacks:

final title = responseTitle ?? 'Untitled';

Use ?. for safe access:

final city = user.address?.city;

Use if checks when the next step genuinely requires the value:

final email = formEmail;

if (email == null || email.isEmpty) {
  return 'Email is required';
}

return sendInvite(email);

Use explicit parsing for API responses:

String readRequiredString(Map<String, dynamic> json, String key) {
  final value = json[key];
  if (value is String && value.isNotEmpty) {
    return value;
  }
  throw FormatException('Missing required string: $key');
}

Form and API Example

Imagine a signup form where the API returns an optional phone number.

class SignupResponse {
  const SignupResponse({
    required this.userId,
    required this.email,
    this.phoneNumber,
  });

  final String userId;
  final String email;
  final String? phoneNumber;

  factory SignupResponse.fromJson(Map<String, dynamic> json) {
    final userId = json['userId'];
    final email = json['email'];
    final phoneNumber = json['phoneNumber'];

    if (userId is! String || email is! String) {
      throw const FormatException('Invalid signup response');
    }

    return SignupResponse(
      userId: userId,
      email: email,
      phoneNumber: phoneNumber is String ? phoneNumber : null,
    );
  }
}

The model says exactly what the API guarantees and what it does not. That is the point of null safety.

If null safety errors are spreading through a Flutter codebase, Beyond Just Digital can help clean up the models and API boundaries instead of patching each crash one by one.