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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Creating Model Classes

For this project, you’ll create three models: Friend, FamilyMember and User. These model classes will help you hold and manage user-entered data.

User‘s main responsibility is to hold Friend, FamilyMember and primitive information about the user.

All three extend an abstract class, Person, which defines fields common to these models. This class is already defined for you in the starter project.

Reviewing the Person Class

Project models relationship

Inside lib/model/abstract, open person.dart. Person‘s constructor takes a set of required properties, namely: name, surname, birth date and gender:

abstract class Person {
  String name;
  String surname;
  String birthDate;
  String gender;

  //1
  Person({
    required this.name,
    required this.surname,
    required this.birthDate,
    required this.gender});

  //2
  abstract String whoAmI;

}

Here’s how it works:

  1. You need to initialize every class property in Dart. If you can’t initialize the property via a class constructor, you must declare it as a nullable type. By using the required keyword, you make the property required so you don’t have to declare it as nullable.
  2. Dart treats abstract classes differently. It gives you a compile-time error if you don’t initialize fields or make them nullable. To allow the fields to be implemented and to prevent compile-time errors, you mark the fields as abstract, which lets the child class implement them.

Creating the User Class

Create user.dart under lib/model and extend it from Person:

class User extends Person {
  User() : super();

  @override
  String whoAmI;
}

Because Person requires a set of parameters, you pass these parameters from User to Person using super(). The final class should look like this:

class User extends Person {

  User({required String name,
       required String surname,
       required String birthDate,
       required String gender})
     : super(name: name, surname: surname, birthDate: birthDate, gender: gender);

  @override
  String whoAmI = ' a user';

}

Creating Friend and FamilyMember Classes With Nullable Types

Add a new file friend.dart under lib/model. Then create a class called Friend extending it from Person:

import 'abstract/person.dart';


 class Friend extends Person {
 //1
  Friend(
     {required String name,
       required String surname,
       required String birthDate,
       required String gender})
     :super(name: name, surname: surname, birthDate: birthDate, gender: gender);
  //2
  @override
  String whoAmI = 'a friend';

}

In the code above, you:

  1. Declare Friend with all required arguments, then call super with the arguments.
  2. Implement an abstract field based on Friend.

Declaring Nullable Types

In real life, a friend has a relationship with the user. They could be a high school friend, a colleague, or a next-door neighbor. To define the relationship, add a nullable called relation to Friend:

String? relation;

Dart uses the nullable operator ?, to declare nullable types. Thus, you just need to append ? to the variable type and it becomes nullable.

Note: In null-safe Dart, you can’t define class properties without initialization or you have to make them nullable. If you remove the ? symbol from relation, Dart complains that it isn’t initialized and forces you to make it nullable or initialize it with a value.

Now, add relation to Friend as an optional argument:

String? relation;

Friend(
     {required String name,
       required String surname,
       required String birthDate,
       required String gender, this.relation})
     :super(name: name, surname: surname, birthDate: birthDate, gender: gender);

You didn’t mark relation as required in the constructor because you want to be able to create an instance of a Friend without declaring the relationship. Dart will assign null to it at runtime.

Creating FamilyMember With a Nullable Property

Create family_member.dart under lib/model and extend it from Person. Then declare a nullable profession field.

import 'abstract/person.dart';

class FamilyMember extends Person {
  String? profession;

  FamilyMember(
     {required String name,
       required String surname,
       required String birthDate,
       required String gender, this.profession})
     : super(name: name, surname: surname, birthDate: birthDate, gender: gender);

  @override
  String whoAmI = 'a family member';

}

Here, you’re extending Person and implementing its abstract variable, whoAmi. You also added profession. This property is nullable because a person may or may not have a profession.

Using Late Variables and Lazy Initialization

To display friends and family members on the home screen, you need to create them in _AddMemberPageState. This class is in lib/add_member_page.dart.

Add late Person _person inside _addMember() in _AddMemberPageState:

void _addMember() {
  late Person _person;
}

This object will hold a Friend or FamilyMember.

Use late on variables when you’re sure you’ll initialize them before using them. Use late with class properties.

Sometimes, you can’t initialize properties in the constructor, but you’ll define them in other methods of your class. In that case, you mark those properties with late.

Another advantage of late is lazy initialization. Dart will not initialize late properties until they’re used for the first time. This can be useful during app initialization, when an expression is costly or might not be needed.

Retrieving Data From Widgets

To create a _person object, you need to retrieve data from the widgets. To do that, update _addMember() to the following:

void _addMember() {
  //1
  late final Person _person;
  final name = _nameController.text;
  final surname = _surnameController.text;
  final birthDate = _birthDateController.text;
  final gender = _dropDownGender;
  final profession = _professionController.text;
  final friendRelation = _friendController.text;
  //2
  if (_dropDownMember.contains(ProjectConst.FAMILY_MEMBER)) {
    _person = FamilyMember(
      name: name,
      surname: surname,
      birthDate: birthDate,
      gender: gender,
      profession: profession.isEmpty ? null : profession,
    );
  } else {
    _person = Friend(
        name: name,
        surname: surname,
        birthDate: birthDate,
        gender: gender,
        relation: friendRelation.isEmpty ? null : friendRelation);
  }
  //3
  DataManager.addPerson(_person);
  Navigator.pop(context);
}

Here’s what’s happening:

  1. You retrieve user-entered information from the text fields.
  2. Based on the type of relationship the user selected, you create a Friend or FamilyMember. Pay attention to the last property of each object. If relation or profession is empty, it passes null because you defined these properties as nullable.
  3. DataManager is already defined in lib/utils/data_manager.dart. It adds a _person into a static list so you can access it from the home screen. Navigator.pop() closes the current screen.

Build and run. You can now add members to the list:

Empty Add member screen

But on the home screen, you can’t see members in the Friends or Family members sections:

Home Page with empty information

You’ll see how to fix that shortly. Before that, take a look at Dart’s Never type.