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
relationisnull. If it isn’t null, the condition returnsrelation. - If
relationisnull, the condition callsrelationsIsNotDefined(), which throws anArgumentErrorexception. Notice how the code is not wrapped in a try-catch statement.Neversignals 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
peopleto get onlyFamilyMembers, then puts their names intonames. - Checks if
personis of typeFamilyMember. Notice how you don’t have to castpersontoFamilyMember. Automatic type promotion takes care of that. Dart automatically promotespersontoFamilyMember, allowing you to access its properties and methods. - Similar to the method you used to get
FamilyMembers, except it getsFriends instead. - Checks if
personis of typeFriend, then, if true, automatically promotespersonto 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
TextFields. - Creates a new
_user. - Assigns a list of people to
_user. However, before you assign anything, you need to ensure that_userisn’tnull. To do this, you use the Postfix null assertion bang operator,!, to cast the nullable_userto its non-nullable type. This is called casting away nullability. But if_useris notnullfrom this code, why do you need to cast it to a non-nullable type? Because you declared that_useris nullable. Consequently, Dart believes it might have been assigned anullsomewhere 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.




