Non-Nullable Dart: Understanding Null Safety
Learn how to use null safety in Dart. Get to know Dart’s type system and how to utilize language features in production code. By Sardor Islomov.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Non-Nullable Dart: Understanding Null Safety
30 mins
- Getting Started
- Getting to Know the App
- Understanding Sound Null Safety
- Exploring the Dart Type System
- Enabling Null Safety
- Migrating to Null-Safe Dart
- Creating Model Classes
- Reviewing the Person Class
- Creating the User Class
- Creating Friend and FamilyMember Classes With Nullable Types
- Declaring Nullable Types
- Creating FamilyMember With a Nullable Property
- Using Late Variables and Lazy Initialization
- Retrieving Data From Widgets
- Understanding the Never Type
- Understanding Flow Analysis
- Testing the Never Type
- Using Type Promotion
- Displaying Member Names on the Home Screen
- Displaying User Details in the Dialog
- Retrieving User Input From the TextFields
- Displaying the Dialog With the User Information
- Displaying the User Relationship
- Clearing the UI
- Sound Null Safety In a Nutshell
- Where to Go From Here?
Understanding the Never Type
Never
is at the bottom of the Dart type system. It has no value. You don’t really use Never
in your code; when an expression returns Never
, the program will throw an exception or abort when the execution reaches it.
However, for the sake of this tutorial, you’ll use Never
to test a scenario when the app promotes errors to the user interface because it encounters an unhandled exception.
Add checkRelation()
to Friend
. This method checks whether relation
is defined:
String? checkRelation() {
//1
if (relation != null) {
return relation;
//2
} else {
relationIsNotDefined();
}
}
Never relationIsNotDefined() {
throw ArgumentError('Friend relation is not defined');
}
This is how the method works:
- This condition checks whether
relation
isnull
. If it isn’t null, the condition returnsrelation
. - If
relation
isnull
, the condition callsrelationsIsNotDefined()
, which throws anArgumentError
exception. Notice how the code is not wrapped in a try-catch statement.Never
signals flow analysis that the app will throw an exception when it reachesrelationsIsNotDefined()
.
Understanding Flow Analysis
Flow analysis is a mechanism that determines the control flow of a program. Dart uses it most of the time at runtime for type promotion and code reachability analysis.
Flow analysis helps you write null-safe code. By analyzing the code at compile time, it prompts you to handle nullable types better in order to avoid NullPointerExceptions. It comes embedded in the Dart language.
In summary, the main responsibilities of flow analysis are:
- Reachability analysis, which is the process of evaluating a function or expression.
- Code warnings.
- Null checks at compile time and runtime.
- Type promotion.
- Ensuring you assign values to all local and global variables.
Testing the Never Type
Call checkRelation()
inside _addMember()
in _AddMemberPageState
. Before calling checkRelation()
, you need to cast _person
to Friend
using as
. Make it the last call in the else
block:
(_person as Friend).checkRelation();
Build and run. Go to the Add member page and fill in the information:
Don’t provide a value for Friend Relation. Press Add member. Your program should throw an exception.
When you open your Dart analysis terminal, you’ll see the exception:
To add a friend to the list, comment out the call to checkRelation()
. Now, hot restart the app and submit the information. The app will work as it did before.
Using Type Promotion
To display user names on the home screen, you need to write two methods that filter the list of people
into separate groups: friends and family members. Each method appends the names of the people to the names
variable.
Open data_manager.dart inside lib/utils. Define these methods as static in DataManager:
import 'package:profile_app/model/family_member.dart';
import 'package:profile_app/model/friend.dart';
class DataManager {
DataManager._();
static List people = List.empty(growable: true);
static void addPerson(Person person) {
people.add(person);
}
//1
static String getFamilyMemberNames() {
var names = '';
for (var i = 0; i < people.length; i++) {
final person = people[i];
//2
if (person is FamilyMember) {
names += '${person.name} ${person.surname},';
}
}
return names;
}
//3
static String getFriendNames() {
var names = '';
for (var i = 0; i < people.length; i++) {
final person = people[i];
//4
if (person is Friend) {
names += '${person.name} ${person.surname},';
}
}
return names;
}
}
The code above:
- Goes through the list of
people
to get onlyFamilyMember
s, then puts their names intonames
. - Checks if
person
is of typeFamilyMember
. Notice how you don’t have to castperson
toFamilyMember
. Automatic type promotion takes care of that. Dart automatically promotesperson
toFamilyMember
, allowing you to access its properties and methods. - Similar to the method you used to get
FamilyMember
s, except it getsFriend
s instead. - Checks if
person
is of typeFriend
, then, if true, automatically promotesperson
to the typeFriend
. This allows you to access the methods and properties available toFriend
.
Displaying Member Names on the Home Screen
To display the names of the Family members and Friends, you need to define _updateNames()
in _HomePageState
, which you’ll find in home_page.dart.
void _updateNames() {
setState(() {
_friendNames = DataManager.getFriendNames();
_familyMemberNames = DataManager.getFamilyMemberNames();
});
}
This simply assigns friend and family member names to _friendNames
and _familyMemberNames
using DataManager
. setState()
rebuilds the widget tree.
Inside _HomePageState
, you’ll find IconButton
. Call _updateNames()
in .then()
:
IconButton(
icon: const Icon(Icons.add_circle),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) => const AddMemberPage()))
.then((value) => {_updateNames()});
}),
This updates the names in _HomePageState
after you add a member to AddMemberPage
.
Build and run. Go to the Add member page to add a friend:
Add family members to the user profile:
On the home screen, you can now view the people you added in the friend and family sections:
At this point, you’ve set up the two screens, but you still can’t see the member details in the dialog. You’ll address that next.
Displaying User Details in the Dialog
To show user data in the dialog, you first need to finish setting up User
. Specifically, you need to add friendsAndFamily
to User
:
late List friendsAndFamily;
Although friendsAndFamily
could be either nullable or late, you declared it late
to avoid null checks and casting before you use it.
Retrieving User Input From the TextFields
Declare _user
in _HomePageState:
User? _user;
When a user taps Save & Preview, it triggers _displayUserInfo()
. Add this block of code to _displayUserInfo()
to collect the information the user entered:
void _displayUserInfo() {
// 1
final name = _nameController.text;
final surname = _surnameController.text;
final birthDate = _birthDateController.text;
final gender = _dropdownValue;
//2
_user = User(name: name, surname: surname, birthDate: birthDate, gender: gender);
//3
_user!.friendsAndFamily = DataManager.persons;
//4
_showPreview();
}
Here’s what the code does:
- Retrieves user-provided data from the
TextField
s. - Creates a new
_user
. - Assigns a list of people to
_user
. However, before you assign anything, you need to ensure that_user
isn’tnull
. To do this, you use the Postfix null assertion bang operator,!
, to cast the nullable_user
to its non-nullable type. This is called casting away nullability. But if_user
is notnull
from this code, why do you need to cast it to a non-nullable type? Because you declared that_user
is nullable. Consequently, Dart believes it might have been assigned anull
somewhere in the code. To be on the safe side, Dart requires you to cast it before you can access its properties. -
_showPreview()
is a predefined method that shows user information in a dialog.