11 final
vs const
in Dart
Both final
and const
in Dart are used to create immutable variables, but they have important differences that affect when and how you should use them. Let’s explore these differences and understand when to use each in class properties.
11.1 Basic Difference
11.1.1 final
A final
variable can only be set once and is initialized when accessed for the first time. This means: - It’s a runtime constant - Its value is determined at runtime - It doesn’t need to be known at compile time
11.1.2 const
A const
variable is a compile-time constant. This means: - Its value must be determined entirely at compile time - It cannot depend on any calculation that needs to happen at runtime - It creates deeply immutable values
11.2 Examples to Illustrate the Difference
// final example
final currentTime = DateTime.now(); // Works! Value determined at runtime
final username = getUserInput(); // Works! Value comes from a function call
// const example
const currentTime = DateTime.now(); // ERROR! Value not known at compile time
const pi = 3.14159; // Works! Value is known at compile time
const greeting = 'Hello, Dart!'; // Works! String literals are compile-time constants
11.3 Complex Examples
Let’s look at how they behave with collections and objects:
// final collections can have mutable content
final List<int> scores = [85, 92, 78];
.add(95); // This works! The list contents can change
scores= [100, 100]; // ERROR! Cannot reassign a final variable
scores
// const collections are deeply immutable
const List<int> topScores = [100, 99, 98];
.add(97); // ERROR! Cannot modify a const list topScores
With custom objects:
class Point {
final int x;
final int y;
const Point(this.x, this.y); // Constructor can be const
}
// Using const constructor
const origin = Point(0, 0); // Compile-time constant
final endpoint = Point(10, 10); // Runtime constant
// This works - different instances
final p1 = Point(1, 1);
final p2 = Point(1, 1);
, p2)); // false - different objects in memory
print(identical(p1
// This creates identical instances - object canonicalization
const cp1 = Point(1, 1);
const cp2 = Point(1, 1);
, cp2)); // true - same object in memory print(identical(cp1
11.4 Usage in Class Properties
Now, let’s address which one to use for class properties:
11.4.1 Use final
for class properties when:
- The value is calculated or determined at runtime
- The value comes from a constructor parameter
- The value is loaded from external sources (API, database, user input)
- The value is a complex object that might need internal modification
class Patient {
final String id;
final String name;
final DateTime admissionDate;
final List<String> symptoms; // List can still be modified internally
this.id, this.name, this.admissionDate, this.symptoms);
Patient(}
11.4.2 Use const
for class properties when:
- The value is a fixed constant known at compile time
- The value never changes across all instances of the class
- The property is static (shared across all instances)
class MedicalConstants {
static const double normalBodyTemp = 37.0; // Celsius
static const int maxHeartRate = 220;
static const List<String> bloodTypes = ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-'];
}
11.5 Best Practices for Classes
11.5.1 Instance Properties
For instance properties (non-static), final
is more commonly used because: - Instance properties often depend on constructor parameters - They frequently represent data that’s determined at runtime - They may need to be unique per instance
class RadiologyReport {
final String patientId;
final DateTime examDate;
final String findings;
final String impression;
// These values come from constructor parameters - use final
this.patientId, this.examDate, this.findings, this.impression);
RadiologyReport(}
11.5.2 Static Class Properties
For static properties that are universal constants: - Use static const
for compile-time constants - Use static final
for runtime constants that are still fixed for the class
class ImagingParameters {
// Compile-time constants - use static const
static const double standardXrayDose = 0.1; // mSv
static const List<String> modalityTypes = ['CT', 'MRI', 'Ultrasound', 'X-ray', 'PET'];
// Runtime constant that's calculated - use static final
static final DateTime serviceStartDate = DateTime(2020, 1, 1);
static final Map<String, double> doseByModality = _initDoseMap();
static Map<String, double> _initDoseMap() {
// Some complex initialization logic
return {
'CT': 7.0,
'X-ray': 0.1,
'PET': 25.0,
};
}
}
11.6 Performance and Memory Considerations
Using const
where appropriate can have performance benefits: - Compile-time constants are optimized by the compiler - Identical const
objects are canonicalized (share the same memory location) - const
collections (lists, maps, sets) are more memory-efficient
11.7 Practical Decision Tree for Class Properties
When deciding between final
and const
for class properties:
- Is the property static (shared across all instances)?
- Yes:
- Is the value known at compile time? → Use
static const
- Is the value determined at runtime but fixed thereafter? → Use
static final
- Is the value known at compile time? → Use
- No (instance property):
- Will it be set in the constructor and never change? → Use
final
- Will it change after initialization? → Use neither (regular property)
- Will it be set in the constructor and never change? → Use
- Yes:
- Is the property a collection (List, Map, Set)?
- Do you need to modify the collection contents? → Use
final
(notconst
) - Is the collection fixed and known at compile time? → Consider
const
- Do you need to modify the collection contents? → Use
11.8 Flutter-Specific Recommendations
In Flutter applications, the choice becomes even more important:
class MyWidget extends StatelessWidget {
// These won't change and are passed from constructor - use final
final String title;
final Color backgroundColor;
// This is a fixed list known at compile time - use const
static const List<String> supportedLanguages = ['en', 'es', 'fr', 'de'];
// Style constants used in this widget - use static const
static const TextStyle headerStyle = TextStyle(fontSize: 18, fontWeight: FontWeight.bold);
const MyWidget({required this.title, required this.backgroundColor});
@override
{
Widget build(BuildContext context) // ...
}
}
Flutter also encourages using const
constructors for widgets when possible, to improve performance through widget canonicalization:
// This creates a new Container instance each build cycle
{
Widget buildNonConstWidget() return Container(
: Colors.blue,
color: Text('Hello'),
child
);}
// This reuses the same widget instance when possible
{
Widget buildConstWidget() return const Container(
: Colors.blue,
color: Text('Hello'),
child
);}
11.9 A Practical Example for Your Medical Context
Let’s tie this together with a more complete class example relevant to your background:
class RadiologyStudy {
// Instance properties from constructor - use final
final String patientId;
final String accessionNumber;
final DateTime studyDate;
final String modality;
final List<String> images; // Note: List contents can still be modified
// Constants known at compile time - use static const
static const List<String> validModalities = ['CT', 'MRI', 'XR', 'US', 'NM', 'MG'];
static const double defaultWindowWidth = 400;
static const double defaultWindowLevel = 40;
// Runtime constants for the class - use static final
static final DateTime systemImplementationDate = DateTime(2022, 5, 15);
static final Map<String, String> modalityDescriptions = {
'CT': 'Computed Tomography',
'MRI': 'Magnetic Resonance Imaging',
'XR': 'X-Ray',
'US': 'Ultrasound',
'NM': 'Nuclear Medicine',
'MG': 'Mammography',
};
// Constructor
{
RadiologyStudy(this.patientId,
required this.accessionNumber,
required this.studyDate,
required this.modality,
required this.images,
required }) {
// Validation could go here
assert(validModalities.contains(modality), 'Invalid modality type');
}
// Method that demonstrates why images is final but not const
void addImage(String imagePath) {
.add(imagePath); // This is allowed because only the reference is final
images}
}
11.10 Summary
final
: Use for values determined at runtime that won’t change after initialization. Ideal for instance properties initialized by constructors.const
: Use for values known at compile time. Ideal for fixed constants, especially static class properties that are shared across all instances.
For your class properties: - Instance properties that come from constructors → final
- Static properties with fixed values → static const
- Static properties initialized at runtime → static final
Remember that both final
and const
prevent reassignment, but only const
creates deeply immutable values. This distinction is particularly important when working with collections and complex objects.