You know that game where you try to find the item that doesn’t belong in a list? Here’s one for you:

horse, camel, pig, cow, sheep, goat

Which one doesn’t belong?

It’s the third one, of course! The other animals are raised by nomadic peoples, but a pig is a farmer’s animal — it doesn’t do so well trekking across the steppe. About now you’re probably muttering to yourself why your answer was just as good — like, a sheep is the only animal with wool, or something similar. If you got an answer that works, good job. Here’s another one:

196, 144, 169, 182, 121

Did you get it? The answer is one hundred and eighty-two. All the other numbers are squares of integers.

One more:

3, null, 1, 7, 4, 5

And the answer is . . . null! All of the other items in the list are integers, but null isn’t an integer.

What? Was that too easy?

Null overview

In the example above, null was the odd value out, but in Dart, it actually fits in… for now. Every type can contain the value of null in addition to its own data type. Here are a few more examples of null in use:

  • double: 3.14159265, 0.001, 100.5, null
  • bool: true, false, null
  • String: 'a', 'hello', 'Would you like fries with that?', null
  • User: ray, vicki, anonymous, null

That means you can set any type to null:

int myInt = null;
double myDouble = null;
bool myBool = null;
String myString = null;
User myUser = null;

If you’re getting the following error:

A value of type 'Null' can't be assigned to a variable of type 'int'

That means you’re from the future. The code above was written back in 2020 before Dart got its major upgrade for sound null safety. Never fear, though, there’s still a lot even you future dwellers can learn in this chapter.

The first two major sections of this chapter, “Null overview” and “Null-aware operators” will describe nullability in Dart as it exists at the time of writing. The final major section, “Null safety preview”, will describe what sound null safety is all about.

What null means

Null means “no value” or “absence of a value”. It’s quite useful to have such a concept. Imagine not having null at all. Say you ask a user for their postal code so that you can save it as an integer in your program:

final postalCode = 12345;
final postalCode = -1;
// Hey everybody, -1 means that the user
// doesn't have a postal code. Don't forget!
final postalCode = -1;
final postalCode = null;

The problem with null

As useful as null is for indicating an absence of a value, developers do have a problem with it. The problem is that they tend to forget that it exists. And when developers forget about null, they don’t handle it in their code. Those nulls are like little ticking time bombs ready to explode your code.

bool isPositive(int anInteger) {
  return !anInteger.isNegative;
}
isPositive(null);
NoSuchMethodError: The getter 'isNegative' was called on null.

Adding an assert

If you’re sure that a function won’t ever get called with null, you add an assert to the beginning of the function:

bool isPositive(int anInteger) {
  assert(anInteger != null);
  return !anInteger.isNegative;
}

Handling null inside a function

The other option is to allow null in the function, but to handle it appropriately. Since null is “no value”, it’s logical to say that it also isn’t positive.

bool isPositive(int anInteger) {
  if (anInteger == null) {
    return false;
  }
  return !anInteger.isNegative;
}
print(isPositive(null));

Null by default

For any variable in Dart, if you don’t initialize the variable, it’ll be given the default value of null. All Dart types, even basic types like int and double, are derived from the type named Object. If you don’t initialize an object, it takes on a null value.

int age;
double height;
String message;
print(age);
print(height);
print(message);

Null-aware operators

Dart has a set of operators that can help you handle null values. Here they are in brief:

If-null operator (??)

One very convenient way to handle null values is to use the double question mark, also known as the if-null operator. This operator says, “If the value on the left isn’t null, then use it; otherwise, go with the value on the right.”

String message;
var text = message ?? 'Error';
Error
message = 'Greetings!';
text = message ?? 'Error';
Greetings!
var text;
if (message == null) {
  text = 'Error';
} else {
  text = message;
}

Null-aware assignment operator (??=)

In the example above, you introduced a new variable named text. However, in that particular case, it was just adding extra complexity. Since you declared message using var, you can reassign it like so:

message = message ?? 'Error';
x = x + 1;
x += 1;
message ??= 'Error';

Null-aware access operator

Earlier with anInteger.isNegative, you saw that trying to access the isNegative property when anInteger was null caused a NoSuchMethodError. There’s also an operator for null safety when accessing object members. The null-aware access operator returns null if the left-hand side is null. Otherwise, it returns the property on the right-hand side.

int age;
print(age?.isNegative);
null
print(age?.toDouble());

Mini-exercises

  1. Create a String variable called profession, but don’t give it a value. Then you’ll have profession null. :]
  2. Use the null-aware access operator to print the length of profession without causing an error.
  3. Pretend you don’t know if profession is null or not, and use the null-aware assignment operator to give profession a value of “basketball player”.

Null safety preview

Welcome to the future!

Getting set up

For those of you still living in the 2.10 Dart ages, when sound null safety hadn’t yet entered the SDK, you’ll need to use another method besides VS Code to test the examples below. DartPad has just the thing. Go to nullsafety.dartpad.dev.

Non-nullable by default

In previous versions of Dart, if you had a variable or parameter or return value, it was null by default if you didn’t initialize it with a value. However, now Dart variables are non-nullable by default. If you don’t initialize a variable, you’ll get an error.

String myString;
print(myString);
The non-nullable local variable 'myString' must be assigned before it can be used
String myString = 'I love non-nullable types!';
3, null, 1, 7, 4, 5

Optional nullability

As you know, though, null isn’t all bad. It’s very useful for indicating “no value”. However, if you do want a nullable value, you have to tell that to Dart. You do that by adding ? after the type name.

String? myString;
print(myString);

Guaranteed value

Since only types that end in a question mark can potentially have a null value, every time you see a type without a question mark, you can be absolutely positive that it won’t ever be null.

bool isPositive(int anInteger) {
  if (anInteger == null) {
    return false;
  }
  return !anInteger.isNegative;
}
The operand can't be null, so the condition is always false
bool isPositive(int anInteger) {
  return !anInteger.isNegative;
}

Initializing non-nullable class fields

Since only nullable variables can be null, Dart requires you to you give a non-nullable member variable in a class an initial value before you use it.

class User {
  String name;
}

Using initializers

One way to initialize a field is to use an initializer value:

class User {
  String name = 'anonymous';
}

Using initializing formals

Another way to initialize a field is to use an initializing formal, that is, by using this in front of the field name:

class User {
  User(this.name);
  String name;
}

Using an initializer list

You can also use an initializer list to instantiate a field variable:

class User {
  User(String name)
    : _name = name;
  String _name;
}

Using default parameter values

You recall that optional parameters default to null if you don’t set them. So for non-nullable types, that means you must provide a default value.

class User {
  User([this.name = 'anonymous']);
  String name;
}
class User {
  User({this.name = 'anonymous'});
  String name;
}

Required named parameters

If you want to make a named parameter required, there’s now a new required keyword. This replaces the @required annotation that you learned about in Chapter 5.

class User {
  User({required this.name});
  String name;
}

Nullable fields

All of the methods above guaranteed that the class field will be initialized, and not only initialized, but initialized with a non-null value. Since the field is non-nullable, it’s not even possible to make the following mistake:

final user = User(name: null);
The argument type 'Null' can't be assigned to the parameter type 'String'
class User {
  String? name;
}

Handling nullable values

The reason that it’s hard to forget to handle null is that if you’re working with nullable values, there isn’t much you can do with them.

String? name;
print(name.length);

Property 'length' cannot be accessed on 'String?' because it is potentially null.

Type promotion

The Dart analyzer, which is the tool that tells you what the compile-time errors and warning are, is smart enough to tell in a wide range of situations if a nullable variable is guaranteed to contain a non-null value or not.

String? name;
name = 'Ray';
print(name.length);
String myMethod(int? myParameter) {
  if (myParameter == null) {
    return 'something';
  }
  return myParameter.toString();
}

More null-aware operators

Previously you learned about the ?? and .? and ??= null-aware operators. The new version of Dart adds some more null-aware operators that will help you to get at a variable’s value if it isn’t null.

Null assertion operator (!)

Sometimes Dart isn’t sure whether a nullable variable is null or not, but you know it’s not. Dart is smart and all, but machines don’t rule the world yet.

String? nullableGreeting = 'hello';
String nonNullableGreeting = nullableGreeting!;
String nonNullableGreeting = nullableGreeting as String;
bool? isBeautiful(String? item) {
  if (item == 'flower') {
    return true;
  } else if (item == 'garbage') {
    return false;
  }
  return null;
}
bool flowerIsBeautiful = isBeautiful('flower');
A value of type 'bool?' can't be assigned to a variable of type bool
bool flowerIsBeautiful = isBeautiful('flower')!;
bool flowerIsBeautiful = isBeautiful('flower') ?? true;

Null-aware cascade operator (?..)

In Chapter 6 you learned about the cascade operator, which allows you to call multiple methods or set multiple properties on the same object.

class User {
  String? name;
  int? id;
}
User user = User()
  ..name = 'Ray'
  ..id = 42;
User? user = User();
user
  ?..name = 'Ray'
  ..id = 42;
String? lengthString = user?.name?.length.toString();

Null-aware index operator (?[])

The null-aware index operator is used for accessing the elements of a list when the list itself might be null. You’ve used lists already a couple of times in this book, but since you won’t cover them in depth until Chapter 8, this section will just give a simple explanation of how the ?[] operator is used.

List<int>? myList = [1, 2, 3];
int? myItem = myList?[0];

The late keyword

Sometimes you want to use a non-nullable type, but you can’t initialize it in any of the ways you learned above.

class User {
  User(this.name);

  final String name;
  final int _secretNumber = _calculateSecret();

  int _calculateSecret() {
    return name.length + 42;
  }
}
late final int _secretNumber = _calculateSecret();

Dangers of being late

The example above was for initializing a final variable, but you can also use late with non-final variables. You have to be careful with this, though. Take a look at the following example:

class User {
  late String name;
}
final user = User();
print(user.name);
LateInitializationError: Field 'name' has not been initialized.

Benefits of being lazy

Who knew that it pays to be lazy sometimes? Dart knows this, though, and uses it to great advantage.

class SomeClass {
  late String? value = doHeavyCalculation();
  String? doHeavyCalculation() {
    // do heavy calculation
  }
}

Challenges

Before moving on, here are some challenges to test your knowledge of nullability. It’s best if you try to solve them yourself, but solutions are available if you get stuck. These are available with the supplementary materials for this book.

Challenge 1: Random nothings

Write a function that randomly returns 42 or null. Assign the return value of the function to a variable named result that will never be null. Give result a default of 0 if the function returns null.

Challenge 2: Naming customs

People around the world have different customs for giving names to children. It would be difficult to create a data class to accurately represent them all, but try it like this:

Key points

Points ending with an asterisk (*) only apply to the sound null safety update in Dart, not to Dart 2.10 or earlier.

??    if-null operator
??=   null-aware assignment operator
?.    null-aware access operator
?.    null-aware method invocation operator
!     null assertion operator*
?..   null-aware cascade operator*
?[]   null-aware index operator*
...?  null-aware spread operator

Where to go from here?

Keep your eyes open for the big sound null safety upgrade in Dart. Most of the examples in the other chapters of this book will still work, but for a few of them, you’ll need to make some minor adjustments, such as adding ? after the type name to make it nullable.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.