Records (Dart 3)
Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 86 of 110.
Dart 3 introduced records - a lightweight way to bundle multiple values together without creating a full class. Records are immutable, have built-in equality, and are perfect for returning multiple values from a function or grouping related data temporarily.
Create a record by wrapping values in parentheses:
void main() {
// A simple record with two values
var point = (10, 20);
print(point.$1); // 10 - access first element
print(point.$2); // 20 - access second element
// Records with named fields
var person = (name: 'Alice', age: 30);
print(person.name); // Alice
print(person.age); // 30
}Positional fields are accessed with $1, $2, etc., while named fields use their names directly. You can mix both styles in a single record.
Records shine when returning multiple values from functions - no need to create a class just to return two pieces of data:
(int, int) divideWithRemainder(int a, int b) {
return (a ~/ b, a % b);
}
(String name, int age) getUser() {
return ('Bob', 25);
}
void main() {
var (quotient, remainder) = divideWithRemainder(17, 5);
print('$quotient remainder $remainder'); // 3 remainder 2
var (name, age) = getUser();
print('$name is $age'); // Bob is 25
}Records automatically support equality based on their values, making them convenient for comparisons without implementing == yourself. Use records when you need simple, immutable data groupings; use classes when you need methods, mutability, or more complex behavior.
Challenge
EasyLet's build a coordinate system using records! You'll create functions that work with 2D points represented as records, demonstrating how records make it easy to return and work with multiple related values without creating full classes.
You'll organize your code into two files:
geometry.dart: Create functions that use records to work with coordinate data:- A function
createPoint(int x, int y)that returns a record with named fieldsxandy. The return type should be({int x, int y}) - A function
midpoint(({int x, int y}) p1, ({int x, int y}) p2)that calculates and returns the midpoint between two points as a record with named fields. Use integer division (~/) for the calculation - A function
distance(({int x, int y}) p1, ({int x, int y}) p2)that returns a record with positional fields: the horizontal distance (difference in x) and vertical distance (difference in y). The return type should be(int, int). Calculate asp2.x - p1.xandp2.y - p1.y
- A function
main.dart: Import your geometry file and demonstrate records in action:- Create two points using
createPoint: point A at(2, 4)and point B at(10, 8) - Print
Point A: ([x], [y])using the named fields - Print
Point B: ([x], [y])using the named fields - Calculate the midpoint and print
Midpoint: ([x], [y]) - Calculate the distance and use destructuring to extract the values into variables
dxanddy, then printDistance: dx=[dx], dy=[dy]
- Create two points using
Notice how records let you return multiple values cleanly - named fields for clarity when the meaning matters, and positional fields when the context is obvious!
Expected output:
Point A: (2, 4)
Point B: (10, 8)
Midpoint: (6, 6)
Distance: dx=8, dy=4Cheat sheet
Dart 3 introduced records - a lightweight way to bundle multiple values together without creating a full class. Records are immutable and perfect for returning multiple values from a function.
Create a record by wrapping values in parentheses:
// Simple record with positional fields
var point = (10, 20);
print(point.$1); // 10 - access first element
print(point.$2); // 20 - access second element
// Records with named fields
var person = (name: 'Alice', age: 30);
print(person.name); // Alice
print(person.age); // 30Positional fields are accessed with $1, $2, etc., while named fields use their names directly.
Records are ideal for returning multiple values from functions:
// Return type specifies record structure
(int, int) divideWithRemainder(int a, int b) {
return (a ~/ b, a % b);
}
// Named fields in return type
(String name, int age) getUser() {
return ('Bob', 25);
}
// Destructuring records
var (quotient, remainder) = divideWithRemainder(17, 5);
print('$quotient remainder $remainder'); // 3 remainder 2
var (name, age) = getUser();
print('$name is $age'); // Bob is 25Record return types with named fields use curly braces:
({int x, int y}) createPoint(int x, int y) {
return (x: x, y: y);
}Records automatically support equality based on their values.
Try it yourself
import 'geometry.dart';
void main() {
// TODO: Create point A at (2, 4) using createPoint
// TODO: Create point B at (10, 8) using createPoint
// TODO: Print Point A using named fields: Point A: ([x], [y])
// TODO: Print Point B using named fields: Point B: ([x], [y])
// TODO: Calculate the midpoint and print: Midpoint: ([x], [y])
// TODO: Calculate distance and use destructuring to extract into dx and dy
// Then print: Distance: dx=[dx], dy=[dy]
}
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