How to use enum in dart
Tags: flutter, dart, tutorial
Published: 2022-12-04 15:17:16

## 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. 

```dart
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](https://api.flutter.dev/flutter/dart-core/Enum-class.html), the desugaring version of code above is:

```dart
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.

```dart
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.

```dart
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

```dart
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](https://dart.dev/guides/language/extension-methods). 

```dart
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](https://api.dart.dev/stable/2.18.5/dart-core/EnumName.html) to do the very same thing.

```dart
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](https://github.com/dart-lang/language/blob/d16704ae6c9a5fd8f30b7ba9d33a5ab650c7447d/accepted/future-releases/enhanced-enums/feature-specification.md), 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. 

```dart
// 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.

```dart
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

```dart
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`](https://api.dart.dev/stable/dart-core/Enum-class.html) class.
- All [generative constructors](https://dart.dev/guides/language/language-tour#constant-constructors) (constructors that generate instance of class) must be constant. this ensures all enum instances are constant value .
- [Factory constructors](https://dart.dev/guides/language/language-tour#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 the  `hasCode` getters, as well as  the `== operator`
- A static or instance member named `values` are not allowed, as it would conflict with the default static `values` getter.