Null Safety in Classes
Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 28 of 110.
When designing classes, you need to decide which fields are required and which are optional. Null safety affects how you declare instance variables and write constructors.
Non-nullable fields must be initialized before the constructor body runs. You can do this through initializer lists, constructor parameters, or default values:
class User {
String name; // Must be initialized
String? bio; // Optional - can be null
int age = 0; // Has default value
User(this.name); // name is initialized via parameter
}For nullable fields, you can leave them uninitialized since they default to null. This is useful for optional data:
class Product {
String title;
String? description; // Optional product description
double? discount; // No discount by default
Product(this.title);
}
var item = Product('Laptop');
print(item.description); // null
print(item.discount); // nullWhen accessing nullable fields, use the null-aware operators you've learned:
class Order {
String id;
User? customer;
Order(this.id);
String getCustomerName() {
return customer?.name ?? 'Guest';
}
}The required keyword in named parameters ensures callers provide essential values:
class Account {
String email;
String? phone;
Account({required this.email, this.phone});
}
var acc = Account(email: 'test@mail.com'); // phone is optionalBy thoughtfully choosing between nullable and non-nullable fields, you create classes that clearly communicate which data is essential and which is optional.
Challenge
EasyLet's build a booking system for a hotel that demonstrates how to design classes with a thoughtful mix of required and optional fields using null safety.
You'll organize your code into two files:
reservation.dart: Define aReservationclass that represents a hotel booking. Every reservation needs certain essential information, but some details are optional extras that guests may or may not provide. Your class should have:- A non-nullable
String guestName- every reservation must have a guest name - A non-nullable
int roomNumber- every reservation must be assigned a room - A non-nullable
int nightswith a default value of1- minimum stay is one night - A nullable
String? specialRequests- guests may have special requests - A nullable
DateTime? checkInTime- exact arrival time might not be known - A constructor using named parameters with
requiredforguestNameandroomNumber, whilenightsshould be optional with its default value - A method
getRequestsSummary()that returns the special requests if provided, or'No special requests'using the??operator - A method
getArrivalInfo()that returns the check-in time's string representation using?., ornullif not set - A method
printConfirmation()that displays the reservation details
- A non-nullable
main.dart: Import your reservation class and create bookings that showcase different combinations of required and optional data:- Create a reservation for guest
'Sarah Johnson'in room101for3nights, with special requests'Late checkout'and check-in time set toDateTime(2024, 6, 15, 14, 30) - Create a reservation for guest
'Mike Chen'in room205(use default nights), with only special requests set to'Extra pillows' - Create a reservation for guest
'Emma Davis'in room310for2nights (leave both optional fields as null) - For each reservation in order, call
printConfirmation(), then printArrival: [result]usinggetArrivalInfo()
- Create a reservation for guest
The printConfirmation() method should print in this format:
=== Reservation Confirmed ===
Guest: [guestName]
Room: [roomNumber]
Nights: [nights]
Requests: [getRequestsSummary()]Expected output:
=== Reservation Confirmed ===
Guest: Sarah Johnson
Room: 101
Nights: 3
Requests: Late checkout
Arrival: 2024-06-15 14:30:00.000
=== Reservation Confirmed ===
Guest: Mike Chen
Room: 205
Nights: 1
Requests: Extra pillows
Arrival: null
=== Reservation Confirmed ===
Guest: Emma Davis
Room: 310
Nights: 2
Requests: No special requests
Arrival: nullCheat sheet
Non-nullable fields must be initialized before the constructor body runs, while nullable fields can remain uninitialized (defaulting to null):
class User {
String name; // Must be initialized
String? bio; // Optional - defaults to null
int age = 0; // Has default value
User(this.name);
}Use nullable fields for optional data:
class Product {
String title;
String? description; // Optional
double? discount; // Optional
Product(this.title);
}
var item = Product('Laptop');
print(item.description); // nullAccess nullable fields with null-aware operators:
class Order {
String id;
User? customer;
Order(this.id);
String getCustomerName() {
return customer?.name ?? 'Guest';
}
}Use required keyword in named parameters for essential values:
class Account {
String email;
String? phone;
Account({required this.email, this.phone});
}
var acc = Account(email: 'test@mail.com');Try it yourself
import 'reservation.dart';
void main() {
// TODO: Create reservation for 'Sarah Johnson'
// Room 101, 3 nights, special requests: 'Late checkout'
// Check-in time: DateTime(2024, 6, 15, 14, 30)
// TODO: Create reservation for 'Mike Chen'
// Room 205, use default nights, special requests: 'Extra pillows'
// No check-in time specified
// TODO: Create reservation for 'Emma Davis'
// Room 310, 2 nights, no special requests, no check-in time
// TODO: For each reservation in order:
// 1. Call printConfirmation()
// 2. Print 'Arrival: [result]' using getArrivalInfo()
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor