## Dart Apprentice

#### Before You Begin

Section 0: 4 chapters

# 7 Nullability Written by Jonathan Sande

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

As out of place as `null` looks in that list of integers, many computer languages actually include it. In the past Dart did, too, but as of version 2.12, Dart decided to take `null` out of the list and only put it back if you allow Dart to do so. This feature is called sound null safety, but to find out what was so dangerous about `null` in the first place, you’ll have to keep reading.

### 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:

``````int postalCode = 12345;
``````
``````int postalCode = -1;
``````
``````// Hey everybody, -1 means that the user
// doesn't have a postal code. Don't forget!
int postalCode = -1;
``````
``````int postalCode = null;
``````
``````A value of type 'Null' can't be assigned to a variable of type 'int'.
``````

### The problem with null

As useful as `null` is for indicating the 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 the code.

``````environment:
sdk: '>=2.10.0 <3.0.0'
``````
``````void main() {
print(isPositive(3));  // true
print(isPositive(-1)); // false
}

bool isPositive(int anInteger) {
return !anInteger.isNegative;
}
``````
``````print(isPositive(null));
``````
``````NoSuchMethodError: The getter 'isNegative' was called on null.
``````
``````environment:
sdk: '>=2.12.0 <3.0.0'
``````

### Nullable vs. non-nullable types

Starting with version 2.12, Dart separated its types into nullable and non-nullable. Nullable types end with a question mark (`?`) while non-nullable types do not.

#### Non-nullable types

Dart types are non-nullable by default. That means they’re guaranteed to never contain the value `null`, which is the essence of the meaning of sound in the phrase “sound null safety”. These types are easy to recognize because, unlike nullable types, they don’t have a question mark at the end.

``````int myInt = 1;
double myDouble = 3.14159265;
bool myBool = true;
String myString = 'Hello, Dart!';
User myUser = User(id: 42, name: 'Ray');
``````
``````int postalCode = null; // error
``````

#### Nullable types

A nullable type can contain the `null` value in addition to its own data type. You can easily tell the type is nullable because it ends with a question mark (`?`), which is like saying, “Maybe you’ve got the data you want or maybe you’ve got `null`. That’s the question.” Here are some example values that nullable types could contain:

``````int? myInt = null;
double? myDouble = null;
bool? myBool = null;
String? myString = null;
User? myUser = null;
``````
``````int? age;
double? height;
String? message;
``````
``````print(age);
print(height);
print(message);
``````

### Mini-exercises

1. Create a `String?` variable called `profession`, but don’t give it a value. Then you’ll have `profession` `null`. :]
2. Give `profession` a value of “basketball player”.
3. Write the following line and then hover your cursor over the variable name. What type does Dart infer `iLove` to be?
``````const iLove = 'Dart';
``````

## Handling nullable types

The big problem with the old nullable types in the past was how easy it was to forget to add code to handle `null` values. That’s no longer true. Dart now makes it impossible to forget because you really can’t do much at all with a nullable value until you’ve dealt with the possibility of `null`.

``````String? name;
print(name.length);
``````
``````The property 'length' can't be unconditionally accessed because the receiver can be '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);
``````

#### Flow analysis

Type promotion works for more than just the trivial example above. Dart uses sophisticated flow analysis to check every possible route the code could take. As long as none of the routes come up with the possibility of `null`, it’s promotion time!

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

### Null-aware operators

In addition to flow analysis, Dart also gives you a whole set of tools called null-aware operators that can help you handle potentially 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;
final text = message ?? 'Error';
``````
``````String text;
if (message == null) {
text = 'Error';
} else {
text = message;
}
``````

#### Null-aware assignment operator (`??=`)

In the example above, you had two variables: `message` and `text`. However, another common situation is when you have a single variable that you want to update if its value is `null`.

``````double? fontSize;
``````
``````fontSize = fontSize ?? 20.0;
``````
``````x = x + 1;
x += 1;
``````
``````fontSize ??= 20.0;
``````

#### 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());
``````

#### 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 nonNullableString = myNullableString!;
``````
``````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') as bool;
``````
``````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
?..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];
``````
``````myList = null;
``````
``````int? myItem = myList?[2];
``````

### Initializing non-nullable fields

When you create an object from a class, Dart requires you to initialize any non-nullable member variables before you use them.

``````class User {
String name;
}
``````

#### Using initializers

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

``````class User {
String name = 'anonymous';
}
``````

#### Using initializing formals

Another way to initialize a property 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 set a field variable:

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

#### Using default parameter values

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

As you learned in Chapter 5, if you want to make a named parameter required, use the `required` keyword.

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

### Nullable instance variables

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 {
User({this.name});
String? name;
}
``````

#### No promotion for non-local variables

One topic that people often get confused about is the lack of type promotion for nullable instance variables.

``````bool isLong(String? text) {
if (text == null) {
return false;
}
return text.length > 100;
}
``````
``````class TextWidget {
String? text;

bool isLong() {
if (text == null) {
return false;
}
return text.length > 100; // error
}
}
``````
``````The property 'length' can't be unconditionally accessed because the receiver can be 'null'.
``````
``````bool isLong() {
if (text == null) {
return false;
}
return text!.length > 100;
}
``````
``````class TextWidget {
String? text;

bool isLong() {
final text = this.text; // shadowing
if (text == null) {
return false;
}
return text.length > 100;
}
}
``````

### 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;
}
}
``````
``````The instance member '_calculateSecret' can't be accessed in an initializer.
``````
``````late final int _secretNumber = _calculateSecret();
``````
``````class User {
User(this.name) {
_secretNumber = _calculateSecret();
}
late final int _secretNumber;
// ...
}
``````

#### 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 with the supplementary materials for this book if you get stuck.

### 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

• Null means “no value.”
• A common cause of errors for programming languages in general comes from not properly handling `null`.
• Dart 2.12 introduced sound null safety to the language.
• Sound null safety distinguishes nullable and non-nullable types.
• A non-nullable type is guaranteed to never be `null`.
• Null aware operators help developers to gracefully handle `null`.
``````??    if-null operator
??=   null-aware assignment operator
?.    null-aware access operator
?.    null-aware method invocation operator
!     null assertion operator
?[]   null-aware index operator