Basic usage
Enumerated types are very common data type in programming languages. It's used to represent a fixed number of constant values. In dart, you can declare a enumerated type with the enum
keyword.
enum Pet { cat, dog, fish }
void main() {
final values = Pet.values;
print(values); // [Pet.cat, Pet.dog, Pet.fish]
print(Pet.cat); // Pet.cat
print(Pet.dog.index); // 1
print(Pet.fish == values[2]); // true
}
All enums implicitly extend the Enum class, the desugaring version of code above is:
class Pet extends Enum {
static const Pet cat = Pet._$(0, "cat");
static const Pet dog = Pet._$(1, "dog");
static const Pet fish = Pet._$(2, "fish");
static const List<Pet> values = [cat, dog, fish];
const Pet._$(int _$index, String _$name) : super._(_$index, _$name);
String _$enumToString() => "Pet.${_$name}";
}
abstract class Enum {
Enum._(this.index, this._name);
final int index;
final String _name;
String _$enumToString();
String toString() => _$enumToString();
}
The enum
types are designed to be primitive equality, so enums can be used in switch statement.
void main() {
final pet = Pet.fish;
switch (pet) {
case Pet.cat:
print('Cat');
break;
case Pet.dog:
print('Dog');
break;
default:
print("Fish");
}
}
The enum
types are completely sealed by default. Being sealed means they cannot be subclassed, implemented, mixed in, or otherwise explicitly instantiated.
class FurryPet extends Pet {}
// Error: 'Pet' is an enum and can't be extended or implemented.
class PocketPet implements Pet {}
// Error: 'Pet' is an enum and can't be extended or implemented.
mixin PetMixin on Pet {}
// Error: 'Pet' is an enum and can't be extended or implemented.
const bird = Pet(3, 'Bird');
// Error: Enums can't be instantiated.
Quite simple, right? But what if we wanna get the name of a enum value, or get value by name. We need to do something like below
void main() {
print(Pet.cat.toString().split('.').last); // cat
// names in non-english
const names = ['chat', 'chien', 'poisson'];
print(names[Pet.fish.index]); // poisson
// find enum value by name
final dog = Pet.values.firstWhere((e) => e.toString().split('.').last == 'dog');
print(dog); // Pet.dog
}
Enum extension
Since dart 2.6, we can add custom methods to enum with extension methods.
enum Color {
red,
green,
blue,
}
extension ColorExt on Color {
// names in non-english
String get name {
switch (this) {
case Color.red:
return 'rojo';
case Color.green:
return 'verde';
default:
return 'azul';
}
}
String get hexCode {
const hexCodes = ['#FF0000', '#00FF00', '#0000FF'];
return hexCodes[this.index];
}
}
void main() {
print(Color.green.name); // verde
print(Color.blue.hexCode); // #0000FF
}
As getting the name of a enum value as String is quite often needed, dart 2.15 introduced an official EnumExtension to do the very same thing.
void main() {
// dart < 2.15
print(Color.red.toString().split('.').last) // red
// dart >= 2.15, with official EnumExtension
print(Color.red.name); // red
}
Enhanced enum
Dart 2.17 shipped with the enhanced enum, which makes enum behaves more like a class. Enhanced enum supports generic and can be declared with fields(static or instance member), methods or implements interfaces and applies mixins.
// enhanced enum syntax
enum Name<T extends Object?> with Mixin1, Mixin2 implements Interface1, Interface2 {
id1<int>(args1), id2<String>(args2), id3<bool>(args3);
memberDeclaration*
const Name(params) : initList;
}
Let's rewrite our previous extension method version Color enum into a enhanced one.
enum Color {
red('#FF0000', 'Red'),
green('#00FF00', 'Green'),
blue('#0000FF', 'Blue');
const Color(this.hexCode, this.name);
final String hexCode;
final String name;
@override
String toString() => '$name: $hexCode';
}
void main() {
final blue = Color.blue;
print(blue.hexCode); // #00FF00
print(blue.name); // Blue
print(blue); // Blue: #00FF00
}
Thanks to this feature the code becomes much more clear to read and easy to maintain.
Let's write a more complex one
mixin Pet {
keepCompany() => print('keep you company');
}
enum Dog with Pet implements Comparable<Dog> {
akita.guardDog('Akita', 'japan'), // generate instance with named constructor
bulldog('Bulldog', 'england', false),
chihuahua('Chihuahua', 'mexico', false);
final bool guardDog;
final String name;
final String originCountry;
// generative constructor can only called in the enum
const Dog(this.name, this.originCountry, this.guardDog);
// named constructor with initializers
const Dog.guardDog(String name, String country)
: this.name = name,
this.originCountry = country,
this.guardDog = true;
// factory constructor can only return one of the known values
// redirect to generative constructor is illegal
factory Dog.japanDog() {
return values.firstWhere((value) => value.originCountry == 'japan');
}
// implements the Comparable interface to make it sortable
int compareTo(Dog another) => this.index - another.index;
// override the default toString method
String toString() => name;
}
void main() {
List<Dog> dogs = [Dog.bulldog, Dog.chihuahua, Dog.akita];
dogs.sort((a, b) => a.compareTo(b));
print(dogs);
print(Dog.chihuahua.keepCompany());
}
As enum
is used for fixed number of constant values, there are some restrictions applied to enhanced enum.
- Enhanced enum can not extend class other than the
Enum
class. - All generative constructors (constructors that generate instance of class) must be constant. this ensures all enum instances are constant value .
- Factory constructors can only return one of the fixed, known enum instances.
- All instances must be declared in the beginning of the enum declaration, and there must be at least one instance(empty enum are useless).
- Instance member of enum must be
final
- Override the
index
and thehasCode
getters, as well as the== operator
- A static or instance member named
values
are not allowed, as it would conflict with the default staticvalues
getter.
Top comments (0)