210

In Java when you are defining an enum, you can do something similar to the following, i.e. add members to an enum. Is this possible in Dart?

enum Foo {
  one(1), two(2);
  final num value;
  Foo(this.value);
}
creativecreatorormaybenot
  • 114,516
  • 58
  • 291
  • 402
Digital Deception
  • 2,677
  • 2
  • 15
  • 24
  • I really don't know why Java / Dart world so resist in the implementation of ``enum``. No ``enum`` is not Class / Const, if there is a need to implement ``enum`` as a Class we can still do so depends on situation. A single line of code in ``enum`` requires > 10 lines of code in all other alternatives. So it is not a syntactic sugar – s k Jan 13 '21 at 02:21

11 Answers11

389

Starting with Dart 2.6 you can define extensions on classes (Enums included).

enum Cat {
  black,
  white
}

extension CatExtension on Cat {

  String get name {
    switch (this) {
      case Cat.black:
        return 'Mr Black Cat';
      case Cat.white:
        return 'Ms White Cat';
      default:
        return null;
    }
  }

  void talk() {
    print('meow');
  }
}

Example:

Cat cat = Cat.black;
String catName = cat.name;
cat.talk();

Here's one more live example (uses a constant map instead of a switch): https://dartpad.dartlang.org/c4001d907d6a420cafb2bc2c2507f72c

vovahost
  • 34,185
  • 17
  • 113
  • 116
  • Is it possible to extend enum values? like adding one more value or removing one? – Phani Rithvij Dec 28 '19 at 06:22
  • @PhaniRithvij Yes, it's possible as I showed in the example above. You also have different options to implement this. Check out the dartpad link as well. – vovahost Dec 28 '19 at 10:25
  • No, I mean to change the entries in the original enum. eg. `enum X{a, b};` after defining an extension on it will have 3 entries. i.e. `enum X{a, b, c}` something like `extension MyEnum on X{ /* modify the values here */ }` – Phani Rithvij Dec 28 '19 at 10:32
  • I don't think it's possible. I've looked at the extensions feature [specification](https://github.com/dart-lang/language/blob/master/accepted/2.6/static-extension-members/feature-specification.md#dart-static-extension-methods-design). – Phani Rithvij Dec 28 '19 at 10:33
  • @PhaniRithvij You can't do that. The whole purpose of the enum is to have those static constants. Use a normal class instead. – vovahost Dec 28 '19 at 10:47
  • Is it possible to add a static method like fromString() to enum extension ? – Adam Styrc May 07 '20 at 04:16
  • @AdamStyrc No it's not possible right now. See https://github.com/dart-lang/language/issues/41 – vovahost May 07 '20 at 10:42
  • this is called verbosity. – Renan Coelho Oct 10 '20 at 23:31
  • I was not able to use enum extension with switch cases as it was saying 'case expressions must be constant'. – Sanal Kv Apr 07 '21 at 11:28
201

Dart Enhanced Enum Classes

Starting with Dart 2.17, the Enhanced Enum Classes feature has been introduced. With that, the example from the question would look like this:

enum Foo {
  one(1),
  two(2);

  const Foo(this.value);
  final num value;
}

Now, you can just use the enum class like this:

void main() {
  const foo = Foo.one;
  print(foo.value); // 1
}

Note that you need to update your SDK constraint as the feature requires Dart 2.17:

environment:
  sdk: '>=2.17.0-0 <3.0.0'

Adding members

With enhanced enums, you can add any member to your enum as long as the constructor is const.

This also means that you can add getters or methods to existing enums, for example:

enum Cake {
  cherry,
  apple,
  strawberry;

  String get description => '$name cake';
}

Generics

Enhanced enum classes also enable you to use generics for you enums. If you combine this with members, you can do the following:

enum Bar<T extends Object> {
  number<int>(42),
  name<String>('creativecreatorormaybenot'),
  baz(true); // Note that type inference also works.

  const Bar(this.value);
  final T value;
}

Mixins and interfaces

In addition to declaring members, you can also mixin mixins and implement interfaces with enhanced enums and override any missing implementations.

mixin Foo {
  int get n;
}

abstract class Bar {
  void printNumber();
}

enum Baz with Foo implements Bar {
  one(1),
  two(2);
  
  const Baz(this.n);

  @override
  final int n;

  @override
  void printNumber() => print(n);
}

Multiple arguments

Finally note that even if I did not make use of it in any of the examples above, it is possible to have an arbitrary number of arguments (and an initializer list):

enum Foo {
  bar(42, description: 'The answer to life, the universe, and everything.'),
  baz(0, enabled: false, description: 'noop');

  const Foo(
    int number, {
    this.enabled = true,
    required this.description,
  }) : n = number;
  final int n;
  final bool enabled;
  final String description;
}
croxx5f
  • 5,163
  • 2
  • 15
  • 36
creativecreatorormaybenot
  • 114,516
  • 58
  • 291
  • 402
  • What is a use case for generics? And what about mixins and interfaces? I guess my mind is stuck on the old fashioned enums. – Suragch Jun 24 '22 at 22:33
  • OK, I can think of use cases for mixins and interfaces, but I really can't think of a serious use case for generics. – Suragch Jun 28 '22 at 01:34
  • 2
    @Suragch I would assume all of these features were added for completeness sake without a deeper motivation. Though there are always use cases for generics. Say you want to add a method to the class that behaves differently based on the generic type. – creativecreatorormaybenot Jun 28 '22 at 02:11
  • Generics would be a way to accomplish something similar to Kotlin Sealed Classes I supposed. – Robert Estivill Aug 02 '22 at 10:31
  • You can use enum generics when you want to store data in enums and want to restrict what kind of data it is, same with generic classes. For example, say you wanted an exhaustive way to specify static configuration data for a sublist of pages: https://dartpad.dev/?id=200aded3bd14a91a93ec0a5e9c92ae52 (I'm not sure I would do this specific example in practice, but I could definitely see this concept being very useful in organizing page/routing declarations.) – Abion47 Apr 03 '23 at 18:21
39

Dart enums are used only for the simplest cases. If you need more powerful or more flexible enums, use classes with static const fields like shown in https://stackoverflow.com/a/15854550/217408

This way you can add whatever you need.

GoPro
  • 642
  • 1
  • 10
  • 24
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 4
    With enums combined to a `switch` there's a warning if you don't cover all the enum values. Do you know if there's a way to achieve this behavior with custom classes? For maintainability purposes – Rémi Rousselet Oct 19 '18 at 23:40
  • 1
    There is no way to get this with custom classes. See also https://github.com/dart-lang/sdk/issues/34847 – Günter Zöchbauer Oct 20 '18 at 04:21
34

Nope. In Dart, enums can only contain the enumerated items:

enum Color {
  red,
  green,
  blue
}

However, each item in the enum automatically has an index number associated with it:

print(Color.red.index);    // 0
print(Color.green.index);  // 1

You can get the values by their index numbers:

print(Color.values[0] == Color.red);  // True

See: https://www.dartlang.org/guides/language/language-tour#enums

Argenti Apparatus
  • 3,857
  • 2
  • 25
  • 35
  • `MyEnum.values` is all what I've ever wanted. This coupled with extension methods is enough for me. – om-ha Feb 11 '20 at 15:20
28

It may not be "Effective Dart" , I add a static method inside a Helper class ( there is no companion object in Dart) .

In your color.dart file

enum Color {
  red,
  green,
  blue
}

class ColorHelper{

  static String getValue(Color color){
    switch(color){
      case Color.red: 
        return "Red";
      case Color.green: 
        return "Green";
      case Color.blue: 
        return "Blue";  
      default:
        return "";
    }
  }

}

Since the method is in the same file as the enum, one import is enough

import 'package:.../color.dart';

...
String colorValue = ColorHelper.getValue(Color.red);
Raymond Chenon
  • 11,482
  • 15
  • 77
  • 110
10

I did this (inspired form the accepted answer by @vovahost)

enum CodeVerifyFlow {
  SignUp, Recovery, Settings
}

extension CatExtension on CodeVerifyFlow {
  String get name {
    return ["sign_up", "recovery", "settings"][index];
  }
}

// use it like
CodeVerifyFlow.SignUp.name

thank me later!

Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
Hemant_Negi
  • 1,910
  • 1
  • 20
  • 25
9

extension is good, but it cannot add static methods. If you want to do something like MyType.parse(string), consider using a class with static const fields instead (as Günter Zöchbauer suggested before).

Here is an example

class PaymentMethod {
  final String string;
  const PaymentMethod._(this.string);

  static const online = PaymentMethod._('online');
  static const transfer = PaymentMethod._('transfer');
  static const cash = PaymentMethod._('cash');

  static const values = [online, transfer, cash];

  static PaymentMethod parse(String value) {
    switch (value) {
      case 'online':
        return PaymentMethod.online;
        break;
      case 'transfer':
        return PaymentMethod.transfer;
        break;
      case 'cash':
        return PaymentMethod.cash;
      default:
        print('got error, invalid payment type $value');
        return null;
    }
  }

  @override
  String toString() {
    return 'PaymentMethod.$string';
  }
}

I found this much handier than using a helper function.

final method = PaymentMethod.parse('online');
assert(method == PaymentMethod.online);
Edwin Liu
  • 7,413
  • 3
  • 28
  • 27
  • Indeed, the inability to create some sort of factory based on the values makes the extensions a bit awkward. You can create a static `parse` method on the extension, but then it can only be called with `ExtensionName.parse()`, which is not discoverable for end-users. I'll update my answer to point this out – Bouke Versteegh Sep 07 '20 at 21:42
  • To improve on this solution further, you could store the strings in a `const Map`, and in parse just return the value for that key, and either return null or throw an error. have a look here: https://gist.github.com/boukeversteegh/f67e58f500f3c0c7126640bb43e11197 – Bouke Versteegh Sep 07 '20 at 22:06
8

There's an upcoming feature in Dart known as enhanced enums, and it allows for enum declarations with many of the features known from classes. For example:

enum Blah {
  one(1), two(2);
  final num value;
  const Blah(this.value);
}

The feature is not yet released (and note that several things are not yet working), but experiments with it can be performed with a suitably fresh version of the tools by passing --enable-experiment=enhanced-enums.

The outcome is that Blah is an enum declaration with two values Blah.one and Blah.two, and we have Blah.one.value == 1 and Blah.two.value == 2. The current bleeding edge handles this example in the common front end (so dart and dart2js will handle it), but it is not yet handled by the analyzer.

Erik Ernst
  • 804
  • 7
  • 3
  • Once this feature hits mainstream Dart, I'll mark this answer as the accepted one. However, I'm not doing Dart development or following Dart news, so unless someone notifies me here, there may be a substantial lag time between the two. – Digital Deception Jan 28 '22 at 02:15
  • Current status: The enhanced enums feature is enabled by default in version 2.17.0-266.5.beta, cf. https://dart.dev/get-dart/archive. – Erik Ernst Apr 26 '22 at 10:12
  • Thanks for the update Erik. Unfortunately I've already accepted https://stackoverflow.com/a/71412047/2525150 as it noted when it would hit mainstream and provided a bit more detail on how these can be used. – Digital Deception Apr 27 '22 at 01:33
5

As an improvement on the other suggestions of using Extensions, you can define your assigned values in a list or map, and the extension will be concise.

enum Numbers {
  one,
  two,
  three,
}

// Numbers.one.value == 1
// Numbers.two.value == 2
// Numbers.three.value == 3

example with list

extension NumbersExtensionList on Numbers {
  static const values = [1, 2, 3];
  int get value => values[this.index];
}

example with map

extension NumbersExtensionMap on Numbers {
  static const valueMap = const {
    Numbers.one: 1,
    Numbers.two: 2,
    Numbers.three: 3,
  };
  int get value => valueMap[this];
}

Note: This approach has the limitation that you can not define a static factory method on the Enum, e.g. Numbers.create(1) (as of Dart 2.9). You can define this method on the NumbersExtension, but it would need to be called like NumbersExtension.create(1)

Petr B.
  • 879
  • 1
  • 8
  • 17
Bouke Versteegh
  • 4,097
  • 1
  • 39
  • 35
  • 2
    I think this is the best option because the Map makes it secure for additional values. In the solutions with lists, it will always be dangerous to change/delete/add enum types and corresponding values. – Heikkisorsa Apr 22 '21 at 15:43
0

For String returns :

enum Routes{
  SPLASH_SCREEN,
  HOME,
  // TODO Add according to your context
}

String namedRoute(Routes route){
  final runtimeType = '${route.runtimeTypes.toString()}.';
  final output = route.toString();
  return output.replaceAll(runtimeType, "");
}
-1

You can add extra fields and methods with my package enum_extendable.
It generates extensions on enum, so you can use your enum values in the similar way to instances of a regular Dart class.

For example, if you have enum MathOperator { plus, minus } the symbol and calculate(...) can be added to it.
So, the enum can be used in such way:

final n1 = 1;
final n2 = 2.0;
MathOperator.values.forEach((operator) {
  print('$n1 ${operator.symbol} $n2 = ${operator.calculate(n1, n2)}');
});

Usage:

  1. Add dependencies to pubspec.yaml:

    dependencies:
    enum_extendable_annotation:

    dev_dependencies:
    build_runner:
    enum_extendable_generator:

Install these dependencies:

# Dart
pub get

# Flutter
flutter packages get
  1. Add imports to your enum file:

    import 'package:enum_extendable_annotation/enum_extendable_annotation.dart';

    part '<your enum file name>.enum_extendable.g.dart';

  2. Create a PODO class with fields and methods you wanted.

  3. Create a map with instances of this PODO class for each enum value.

  4. Annotate elements:

  • the enum with @ExtendableEnum();
  • the PODO class - @ExtendableEnumPodo();
  • the map of PODO instances - @ExtendableEnumValues().
  1. Run code generator:
  • if your package depends on Flutter:

    flutter pub run build_runner build

  • if your package does not depend on Flutter:

    dart pub run build_runner build

The file with extensions should be generated.

Example of the enum file:

import 'package:enum_extendable_annotation/enum_extendable_annotation.dart';

part 'math_operator.enum_extendable.g.dart';

@ExtendableEnum()
enum MathOperator { plus, minus }

@ExtendableEnumPodo()
class _MathOperatorPodo {

  final String symbol;
  final num Function(num, num) calculate;

  _MathOperatorPodo(
    this.symbol,
    this.calculate,
  );

  @ExtendableEnumValues()
  static final Map<MathOperator, _MathOperatorPodo> _values = {
  MathOperator.plus: _MathOperatorPodo(
     '+',
      (n1, n2) => n1 + n2,
    ),
  MathOperator.minus: _MathOperatorPodo(
      '-',
      (n1, n2) => n1 - n2,
    ),
  };
}
Oleg
  • 97
  • 6