"Stop writing boring enums. Since Dart 2.17, they're not just values — they're contracts."
Most Flutter/Dart developers use enums like this:
status.dart// 😴 The boring, basic way most devs stop here enum Status { loading, success, error }
Basic switch cases. That's it. But since Dart 2.17, enhanced enums are absolute game-changers — you can add fields, constructors, methods, and getters, turning them into powerful, self-contained data models.
The Hidden Power
Enums in Dart can:
- Hold data as fields
- Expose UI properties like colors, icons, and labels
- Contain business logic as methods
- Act as a single source of truth for your entire feature
Instead of scattering magic strings, UI configs, switch cases, and duplicated logic all over your codebase — you design once and reuse everywhere.
Basic vs Enhanced Enum
before_after.dart// ❌ Before — scattered logic, magic strings, duplicated UI code enum WorkoutType { running, cycling, swimming } String getLabel(WorkoutType type) { switch (type) { case WorkoutType.running: return 'Running'; case WorkoutType.cycling: return 'Cycling'; case WorkoutType.swimming: return 'Swimming'; } } Color getColor(WorkoutType type) { /* another switch... */ } IconData getIcon(WorkoutType type) { /* yet another switch... */ } // ✅ After — everything in one place, type-safe, zero duplication enum WorkoutType { running ('Running', 'km', Colors.orange, Icons.directions_run), cycling ('Cycling', 'km', Colors.blue, Icons.directions_bike), swimming('Swimming', 'm', Colors.cyan, Icons.pool); const WorkoutType(this.label, this.unit, this.color, this.icon); final String label; final String unit; final Color color; final IconData icon; }
Real-World Example — Health & Fitness App
This pattern shines in health, finance, and any data-heavy app. Here's a full example from a real fitness feature, similar to what's used in Zeno: Between Beats:
workout_type.dartimport 'package:flutter/material.dart'; enum WorkoutType { running ('Running', 'km', Color(0xFFFF6B35), Icons.directions_run), cycling ('Cycling', 'km', Color(0xFF4FC3F7), Icons.directions_bike), swimming('Swimming', 'm', Color(0xFF00E5A0), Icons.pool), yoga ('Yoga', 'min', Color(0xFFB39DDB), Icons.self_improvement), hiit ('HIIT', 'min', Color(0xFFFF5370), Icons.flash_on); const WorkoutType( this.label, this.unit, this.color, this.icon, ); final String label; final String unit; final Color color; final IconData icon; // ── Getters ────────────────────────────── Color get backgroundColor => color.withOpacity(0.12); bool get isCardio => this == WorkoutType.running || this == WorkoutType.cycling || this == WorkoutType.swimming; String get formattedUnit => '($unit)'; // ── Methods ─────────────────────────────── Widget getIconWidget({double size = 24}) => Icon( icon, color: color, size: size, ); String formatValue(double value) => '${value.toStringAsFixed(1)} $unit'; }
Using It in the UI
Now your UI code is clean, consistent, and impossible to break with a wrong string:
workout_card.dart// Build a list of workout type chips — zero switch cases needed Wrap( spacing: 8, children: WorkoutType.values.map((type) { return Chip( avatar: type.getIconWidget(size: 18), label: Text(type.label), backgroundColor: type.backgroundColor, labelStyle: TextStyle(color: type.color), ); }).toList(), ) // Display a formatted value with the correct unit Text(WorkoutType.running.formatValue(5.4)) // → "5.4 km" // Check category without any string comparison if (WorkoutType.yoga.isCardio) { /* false — type-safe! */ }
More Patterns — Beyond Workout Types
This same pattern works everywhere in your app:
app_enums.dart// API / network state with display logic enum ApiState<T> { idle ('Idle', false), loading('Loading', true ), success('Done', false), error ('Error', false); const ApiState(this.label, this.isLoading); final String label; final bool isLoading; } // Bottom nav tabs with route + icon in one place enum AppTab { home ('/home', Icons.home, 'Home'), activity('/activity', Icons.bar_chart, 'Activity'), profile ('/profile', Icons.person_outline, 'Profile'); const AppTab(this.route, this.icon, this.label); final String route; final IconData icon; final String label; }
Why This Is a Superpower
- No more magic strings or colors scattered everywhere — everything lives in one clean, type-safe place
- Instant UI consistency — icons, titles, units, colors all tied to the enum value
- Easy to extend — add methods like
getIconWidget()or localization support later - Better refactoring — change once, updates propagate everywhere automatically
- Perfect for dropdowns, charts, tabs, filters in health, finance, or any data-heavy app
- Compiler catches missing cases in exhaustive
switch— no forgotten updates
Why This Matters in Real Apps
When enums own their labels, units, colors, and behavior — your app becomes:
- Type-safe — invalid states become compile errors
- Refactor-friendly — rename one value, the IDE finds every usage
- Easier to scale — adding a new variant is adding one line
- Harder to break — no mismatched UI, no forgotten switch cases
Enums aren't just values. They're contracts.
Stop writing boring enums. Level up your code today! 🚀
✨ By Rohan Kumar Chaudhary | Flutter · iOS · Mobile Developer | Clean Code Advocate 🧼💙
Happy Coding! 💙