Static Members & Enums
Static Members & Enums
In this lesson, we'll explore two powerful features that let you organize class-level data and represent fixed sets of values in a clean, type-safe way.
Part 1: Static Members
So far, every variable and method we've defined belongs to an instance of a class — you create an object, and that object owns its data. But sometimes, data or behavior belongs to the class itself, not to any particular object.
That's where static members come in.
Static Variables (Class-Level State)
A static variable is shared across all instances of a class. There's only one copy, regardless of how many objects you create.
dartclass Counter { static int _count = 0; // Shared by all instances String label; Counter(this.label) { _count++; print('Created counter "$label". Total counters: $_count'); } static int get count => _count; static void reset() { _count = 0; } } void main() { var c1 = Counter('Alpha'); // Total counters: 1 var c2 = Counter('Beta'); // Total counters: 2 var c3 = Counter('Gamma'); // Total counters: 3 print(Counter.count); // 3 — accessed on class, not instance Counter.reset(); print(Counter.count); // 0 }
[!NOTE]
Static variables are accessed using the class name (Counter.count), not via an instance (c1.count would be a warning in strict mode and is bad practice).
Static Methods
A static method belongs to the class, not to an instance. It cannot access instance variables or this.
dartclass MathUtils { // Pure utility — no instance state needed static double square(double x) => x * x; static double cube(double x) => x * x * x; static int clamp(int value, int min, int max) { if (value < min) return min; if (value > max) return max; return value; } static bool isEven(int n) => n % 2 == 0; static List<int> range(int start, int end) { return List.generate(end - start, (i) => start + i); } } void main() { print(MathUtils.square(4)); // 16.0 print(MathUtils.cube(3)); // 27.0 print(MathUtils.clamp(150, 0, 100)); // 100 print(MathUtils.isEven(7)); // false print(MathUtils.range(1, 6)); // [1, 2, 3, 4, 5] }
Static Constants
Static constants are a very common pattern in Dart — they define fixed values associated with a class:
dartclass AppConfig { static const String appName = 'DartShark'; static const String version = '2.1.0'; static const int maxRetries = 3; static const Duration timeout = Duration(seconds: 30); static const String baseUrl = 'https://api.dartshark.dev'; } void main() { print(AppConfig.appName); // DartShark print(AppConfig.version); // 2.1.0 print(AppConfig.maxRetries);// 3 print(AppConfig.baseUrl); // https://api.dartshark.dev }
[!TIP]
Use static const for compile-time constants (values known at compile time). Use static final for runtime constants (computed once at startup, like static final DateTime startTime = DateTime.now()).
When to Use Static Members
| Use Case | Example |
|---|---|
| Utility/helper methods | MathUtils.clamp(), StringUtils.capitalize() |
| Counters / tracking | User._instanceCount++ in constructor |
| Configuration constants | AppConfig.baseUrl |
| Singleton pattern | Database._instance |
| Factory cache | Caching expensive objects |
Cannot Access this in Static Context
Static methods don't belong to any instance, so this is not available:
dartclass Example { String name = 'Dart'; static void badMethod() { // print(this.name); // ❌ Compile error: 'this' is not available in static context // print(name); // ❌ Same error } void goodMethod() { print(this.name); // ✅ Instance method can access instance members } }
Singleton Pattern with Static
A common real-world use of static is the Singleton — ensuring only one instance of a class ever exists:
dartclass DatabaseService { static DatabaseService? _instance; // Private constructor DatabaseService._internal(); // Factory constructor returns the single instance factory DatabaseService() { _instance ??= DatabaseService._internal(); return _instance!; } void query(String sql) { print('Executing: \$sql'); } } void main() { var db1 = DatabaseService(); var db2 = DatabaseService(); print(identical(db1, db2)); // true — same instance! db1.query('SELECT * FROM users'); }
Part 2: Enums
An enum (enumeration) is a special type that represents a fixed set of named constant values. Enums make your code safer and more readable than using raw strings or integers.
Basic Enum
dartenum Color { red, green, blue } enum Direction { north, south, east, west } enum Status { pending, active, inactive, suspended }
Using an enum:
dartvoid main() { Color favoriteColor = Color.blue; print(favoriteColor); // Color.blue // .name — the string name of the value (Dart 2.15+) print(favoriteColor.name); // blue // .index — the zero-based position print(favoriteColor.index); // 2 // All values print(Color.values); // [Color.red, Color.green, Color.blue] }
Switch with Enum
Enums shine when used with switch — Dart can warn you if you forget a case:
dartenum AppStatus { loading, success, error, empty } void handleStatus(AppStatus status) { switch (status) { case AppStatus.loading: print('⏳ Loading data...'); break; case AppStatus.success: print('✅ Data loaded successfully!'); break; case AppStatus.error: print('❌ Something went wrong.'); break; case AppStatus.empty: print('📭 No data found.'); break; } } void main() { handleStatus(AppStatus.success); // ✅ Data loaded successfully! handleStatus(AppStatus.error); // ❌ Something went wrong. }
[!TIP]
Using enums with switch is better than if/else if chains on strings. If you add a new enum value later, Dart will warn you about unhandled cases.
Enhanced Enums (Dart 2.17+)
Dart 2.17 introduced enhanced enums — enums can now have fields, constructors, and methods!
dartenum UserRole { admin(label: 'Administrator', canDelete: true, level: 3), editor(label: 'Editor', canDelete: false, level: 2), viewer(label: 'Viewer', canDelete: false, level: 1); // Fields final String label; final bool canDelete; final int level; // Constructor const UserRole({ required this.label, required this.canDelete, required this.level, }); // Method bool hasHigherPrivilegeThan(UserRole other) => level > other.level; // Getter bool get isAdmin => this == UserRole.admin; } void main() { var role = UserRole.editor; print(role.label); // Editor print(role.canDelete); // false print(role.level); // 2 print(role.isAdmin); // false print(role.hasHigherPrivilegeThan(UserRole.viewer)); // true print(role.hasHigherPrivilegeThan(UserRole.admin)); // false }
Real Example: DayOfWeek Enum
dartenum DayOfWeek { monday('Mon', false), tuesday('Tue', false), wednesday('Wed', false), thursday('Thu', false), friday('Fri', false), saturday('Sat', true), sunday('Sun', true); final String abbreviation; final bool isWeekend; const DayOfWeek(this.abbreviation, this.isWeekend); bool get isWeekday => !isWeekend; DayOfWeek get nextDay { final values = DayOfWeek.values; return values[(index + 1) % values.length]; } } void main() { var today = DayOfWeek.friday; print('Today: \${today.name}'); // Today: friday print('Abbreviation: \${today.abbreviation}'); // Abbreviation: Fri print('Is weekend? \${today.isWeekend}'); // Is weekend? false print('Tomorrow: \${today.nextDay.name}'); // Tomorrow: saturday // Find all weekdays var weekdays = DayOfWeek.values.where((d) => d.isWeekday); print('Weekdays: \${weekdays.map((d) => d.abbreviation).join(', ')}'); // Weekdays: Mon, Tue, Wed, Thu, Fri }
Real Example: AppStatus with Display Info
dartenum AppStatus { loading('Loading...', '⏳'), success('Success!', '✅'), error('An error occurred', '❌'), offline('No internet connection', '📴'); final String message; final String icon; const AppStatus(this.message, this.icon); String get display => '\$icon \$message'; } void showStatus(AppStatus status) { print(status.display); } void main() { showStatus(AppStatus.loading); // ⏳ Loading... showStatus(AppStatus.success); // ✅ Success! showStatus(AppStatus.error); // ❌ An error occurred showStatus(AppStatus.offline); // 📴 No internet connection }
Summary
| Concept | Key Idea |
|---|---|
static variable | Shared across all instances; belongs to the class |
static method | Class-level behavior; no access to this |
static const | Compile-time class constant |
static final | Runtime class constant (computed once) |
this in static | ❌ Not available — static context has no instance |
| Basic enum | Fixed set of named constants |
.name | String name of the enum value |
.index | Zero-based integer position |
.values | List of all enum values |
| Enhanced enum | Enum with fields, constructors, and methods (Dart 2.17+) |
[!IMPORTANT]
Prefer enums over string constants or integer codes whenever you have a fixed set of options. Enums are type-safe, self-documenting, and work beautifully with switch statements.
In the next lesson, we'll unlock Generics — writing type-safe, reusable code that works with any data type.