How do I add Methods or Values to Enums in Dart? - dart
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);
}
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
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;
}
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.
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
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);
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);
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!
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.
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)
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, "");
}
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:
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
Add imports to your enum file:
import 'package:enum_extendable_annotation/enum_extendable_annotation.dart';
part '<your enum file name>.enum_extendable.g.dart';
Create a PODO class with fields and methods you wanted.
Create a map with instances of this PODO class for each enum value.
Annotate elements:
the enum with #ExtendableEnum();
the PODO class - #ExtendableEnumPodo();
the map of PODO instances - #ExtendableEnumValues().
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,
),
};
}
Related
How do I get dynamic class return back from the enum class cases in dart?
I was working on iOS, and now I have to deal with flutter. The case is when I was using swift, I'm able to access the rule variable with different class type based on different enum cases. example code as below: enum SensorTypeRule{ case Lamp(rule:LampRule) case Counter(rule:CounterRule) } struct LampRule{ let ruleTriggerColor: LampColor let ruleSustainedMilliseconds: UInt32 } struct CounterRule{ let ruleLimit: UInt32 } and can be access like below: let sensorTypeRule:SensorTypeRule = someSensorTypeRuleInstance switch sensorTypeRule{ case .Lamp(let rule): print("\(rule. ruleSustainedMilliseconds)") case .Counter(let rule): print("\(rule.ruleLimit)") } Is there an equivalent approach in dart?
Dart does not have the concept of sealed classes, however you can do this way: // Create an abstract class representing an enum // This enum can be instantiated in two ways: either // calling SensorTypeRule.lamp or SensorTypeRule.counter abstract class SensorTypeRule { const factory SensorTypeRule.lamp(LampRule rule) = Lamp._; const factory SensorTypeRule.counter(CounterRule rule) = Counter._; const SensorTypeRule(); } // Create the rules inside each respective class // Using _ as constructor name disallows the user // to instantiate it directly -> Lamp(...) // Instead, it must use the base class -> SensorTypeRule.lamp(...) class Lamp extends SensorTypeRule { final LampRule rule; const Lamp._(this.rule); } class Counter extends SensorTypeRule { final CounterRule rule; const Counter._(this.rule); } // Define the rules class LampRule { final LampColor ruleTriggerColor; final int ruleSustainedMilliseconds; const LampRule({ required this.ruleTriggerColor, required this.ruleSustainedMilliseconds, }); } class CounterRule { final int ruleLimit; const CounterRule({ required this.ruleLimit, }); } When accessing it, you can do this way: final SensorTypeRule sensorTypeRule = SensorTypeRule.counter(CounterRule(ruleLimit: 10)); if (sensorTypeRule is Lamp) { print(sensorTypeRule.rule.ruleSustainedMilliseconds); } else if (sensorTypeRule is Counter) { print(sensorTypeRule.rule.ruleLimit); }
What's the equivalent to this[x] in Dart?
For instance, in Javascript I can do something like: class Foo { x = 'baz'; bar() { const someVar = 'x'; console.log(this[someVar]); // Output: 'baz'; } } Hopefully that's relatively clear - it boils down to accessing a member variable by another variable's contents. How is this achieved in Dart?
This is not trivial in Dart. Dart doesn't have a syntax to access class properties with []. There are a couple of approaches though: Mirrors: https://api.dartlang.org/stable/2.6.1/dart-mirrors/dart-mirrors-library.html Basically you have access to everything and offers the biggest freedom. You can check what properties a class has, access them via names and so on. Big disadvantage is that the generated JS (if targeting web) will be huge. Flutter doesn't support it at all. Reflectable To deal with the large generated JS, you can use package:reflectable. Never tried it with Flutter. It's a bit more to set up and start using bit it works. Dart only solution 1 You can overload [] operator on a class: class Foo { final _backing = <String, String>{ 'foo': 'bar' }; operator [](String val) { return _backing[val]; } } void main() { final inst = Foo(); print(inst['foo']); } Dart only solution 2 Just use a map :) Well sort of... If you are dealing with complex types and you want to add some extra functionality to your map, you can do something like this: import 'dart:collection'; class StringMap extends Object with MapMixin<String, String> { final _backing = <String, String>{}; #override String operator [](Object key) { return _backing[key]; } #override void operator []=(String key, String value) { _backing[key] = value; } #override void clear() { _backing.clear(); } #override Iterable<String> get keys => _backing.keys; #override String remove(Object key) { return _backing.remove(key); } }
Enum from String
I have an Enum and a function to create it from a String because i couldn't find a built in way to do it enum Visibility{VISIBLE,COLLAPSED,HIDDEN} Visibility visibilityFromString(String value){ return Visibility.values.firstWhere((e)=> e.toString().split('.')[1].toUpperCase()==value.toUpperCase()); } //used as Visibility x = visibilityFromString('COLLAPSED'); but it seems like i have to rewrite this function for every Enum i have, is there a way to write the same function where it takes the Enum type as parameter? i tried to but i figured out that i can't cast to Enum. //is something with the following signiture actually possible? dynamic enumFromString(Type enumType,String value){ }
Mirrors aren't always available, but fortunately you don't need them. This is reasonably compact and should do what you want. enum Fruit { apple, banana } // Convert to string String str = Fruit.banana.toString(); // Convert to enum Fruit f = Fruit.values.firstWhere((e) => e.toString() == 'Fruit.' + str); assert(f == Fruit.banana); // it worked Thanks to #frostymarvelous for correcting the answer
As from Dart version 2.15, you can lookup an enum value by name a lot more conveniently, using .values.byName or using .values.asNameMap(): enum Visibility { visible, collapsed, hidden } void main() { // Both calls output `true` print(Visibility.values.byName('visible') == Visibility.visible); print(Visibility.values.asNameMap()['visible'] == Visibility.visible); } You can read more about other enum improvements in the official Dart 2.15 announcement blog post.
My solution is identical to Rob C's solution but without string interpolation: T enumFromString<T>(Iterable<T> values, String value) { return values.firstWhere((type) => type.toString().split(".").last == value, orElse: () => null); } Null safe example using firstWhereOrNull() from the collection package static T? enumFromString<T>(Iterable<T> values, String value) { return values.firstWhereOrNull((type) => type.toString().split(".").last == value); }
Update: void main() { Day monday = Day.values.byName('monday'); // This is all you need } enum Day { monday, tuesday, } Old solution: Your enum enum Day { monday, tuesday, } Add this extension (need a import 'package:flutter/foundation.dart';) extension EnumEx on String { Day toEnum() => Day.values.firstWhere((d) => describeEnum(d) == toLowerCase()); } Usage: void main() { String s = 'monday'; // String Day monday = s.toEnum(); // Converted to enum }
This is all so complicated I made a simple library that gets the job done: https://pub.dev/packages/enum_to_string import 'package:enum_to_string:enum_to_string.dart'; enum TestEnum { testValue1 }; convert(){ String result = EnumToString.parse(TestEnum.testValue1); //result = 'testValue1' String resultCamelCase = EnumToString.parseCamelCase(TestEnum.testValue1); //result = 'Test Value 1' final result = EnumToString.fromString(TestEnum.values, "testValue1"); // TestEnum.testValue1 } Update: 2022/02/10 Dart v2.15 has implemented some additional enum methods that may solve your problems. From here: https://medium.com/dartlang/dart-2-15-7e7a598e508a Improved enums in the dart:core library We’ve made a number of convenience additions to the enum APIs in the dart:core library (language issue #1511). You can now get the String value for each enum value using .name: enum MyEnum { one, two, three } void main() { print(MyEnum.one.name); // Prints "one". } You can also look up an enum value by name: print(MyEnum.values.byName('two') == MyEnum.two); // Prints "true". Finally, you can get a map of all name-value pairs: final map = MyEnum.values.asNameMap(); print(map['three'] == MyEnum.three); // Prints "true".
Using mirrors you could force some behaviour. I had two ideas in mind. Unfortunately Dart does not support typed functions: import 'dart:mirrors'; enum Visibility {VISIBLE, COLLAPSED, HIDDEN} class EnumFromString<T> { T get(String value) { return (reflectType(T) as ClassMirror).getField(#values).reflectee.firstWhere((e)=>e.toString().split('.')[1].toUpperCase()==value.toUpperCase()); } } dynamic enumFromString(String value, t) { return (reflectType(t) as ClassMirror).getField(#values).reflectee.firstWhere((e)=>e.toString().split('.')[1].toUpperCase()==value.toUpperCase()); } void main() { var converter = new EnumFromString<Visibility>(); Visibility x = converter.get('COLLAPSED'); print(x); Visibility y = enumFromString('HIDDEN', Visibility); print(y); } Outputs: Visibility.COLLAPSED Visibility.HIDDEN
Collin Jackson's solution didn't work for me because Dart stringifies enums into EnumName.value rather than just value (for instance, Fruit.apple), and I was trying to convert the string value like apple rather than converting Fruit.apple from the get-go. With that in mind, this is my solution for the enum from string problem enum Fruit {apple, banana} Fruit getFruitFromString(String fruit) { fruit = 'Fruit.$fruit'; return Fruit.values.firstWhere((f)=> f.toString() == fruit, orElse: () => null); }
Here is an alternative way to #mbartn's approach using extensions, extending the enum itself instead of String. Faster, but more tedious // We're adding a 'from' entry just to avoid having to use Fruit.apple['banana'], // which looks confusing. enum Fruit { from, apple, banana } extension FruitIndex on Fruit { // Overload the [] getter to get the name of the fruit. operator[](String key) => (name){ switch(name) { case 'banana': return Fruit.banana; case 'apple': return Fruit.apple; default: throw RangeError("enum Fruit contains no value '$name'"); } }(key); } void main() { Fruit f = Fruit.from["banana"]; print("$f is ${f.runtimeType}"); // Outputs: Fruit.banana is Fruit } Less tedius, but slower If O(n) performance is acceptable you could also incorporate #Collin Jackson's answer: // We're adding a 'from' entry just to avoid having to use Fruit.apple['banana'] // which looks confusing. enum Fruit { from, apple, banana } extension FruitIndex on Fruit { // Overload the [] getter to get the name of the fruit. operator[](String key) => Fruit.values.firstWhere((e) => e.toString() == 'Fruit.' + key); } void main() { Fruit f = Fruit.from["banana"]; print("$f is ${f.runtimeType}"); // Outputs: Fruit.banana is Fruit }
I use this function, I think it's simple and doesn't need any kind of 'hack': T enumFromString<T>(List<T> values, String value) { return values.firstWhere((v) => v.toString().split('.')[1] == value, orElse: () => null); } You can use it like this: enum Test { value1, value2, } var test = enumFromString(Test.value, 'value2') // Test.value2
With Dart 2.15 we can now do this which is much cleaner // Convert to string String fruitName = Fruit.banana.name; // Convert back to enum Fruit fruit = Fruit.values.byName(fruitName); print(fruit); // Fruit.banana assert(fruit == Fruit.banana);
Since Dart 2.17 you can solve this elegantly with Enhanced Enums. (see https://stackoverflow.com/a/71412047/15760132 ) Just add a static method to your enum of choice, like this: enum Example { example1, example2, example3; static Example? fromName(String name) { for (Example enumVariant in Example.values) { if (enumVariant.name == name) return enumVariant; } return null; } } Then you can look for the enum like this: Example? test = Example.fromName("example1"); print(test); // returns Example.example1
I improved Collin Jackson's answer using Dart 2.7 Extension Methods to make it more elegant. enum Fruit { apple, banana } extension EnumParser on String { Fruit toFruit() { return Fruit.values.firstWhere( (e) => e.toString().toLowerCase() == 'fruit.$this'.toLowerCase(), orElse: () => null); //return null if not found } } main() { Fruit apple = 'apple'.toFruit(); assert(apple == Fruit.apple); //true }
I had the same problem with building objects from JSON. In JSON values are strings, but I wanted enum to validate if the value is correct. I wrote this helper which works with any enum, not a specified one: class _EnumHelper { var cache = {}; dynamic str2enum(e, s) { var o = {}; if (!cache.containsKey(e)){ for (dynamic i in e) { o[i.toString().split(".").last] = i; } cache[e] = o; } else { o = cache[e]; } return o[s]; } } _EnumHelper enumHelper = _EnumHelper(); Usage: enumHelper.str2enum(Category.values, json['category']); PS. I did not use types on purpose here. enum is not type in Dart and treating it as one makes things complicated. Class is used solely for caching purposes.
Generalising #CopsOnRoad's solution to work for any enum type, enum Language { en, ar } extension StringExtension on String { T toEnum<T>(List<T> list) => list.firstWhere((d) => d.toString() == this); } String langCode = Language.en.toString(); langCode.toEnum(Language.values);
Simplified version: import 'package:flutter/foundation.dart'; static Fruit? valueOf(String value) { return Fruit.values.where((e) => describeEnum(e) == value).first; } Using the method describeEnum helps you to avoid the usage of the split to get the name of the element.
You can write getEnum like below, getEnum will go through enum values and returns the first enum that is equal to the desired string. Sample getEnum(String name) => Sample.values.firstWhere( (v) => v.name.toLowerCase() == name.toLowerCase(), orElse: () => throw Exception('Enum value not found.'), ); enum SampleEnum { first, second, third } UPDATE also, you can use this: final SampleEnum nameEnum = SampleEnum.values.byName('second'); // SampleEnum.second Usage: void main() { print(getEnum('first')); }
In the latest version of Dart, enum can support custom fields and methods. So the most modern way to do this, is to write a custom field for name/label, and a static parser function. For example: enum Foo { a('FIRST'), b('SECOND'), c('THIRD'), unknown('UNKNOWN'); // make sure the last element ends in `;` final String label; // define a private field const Foo(this.label); // constructor static Foo fromString(String label) { // static parser method return values.firstWhere( (v) => v.label == label, orElse: () => Foo.unknown, ); } } Sample Usage: final foo = Foo.fromString('FIRST'); // will get `Foo.a`
There are a couple of enums packages which allowed me to get just the enum string rather than the type.value string (Apple, not Fruit.Apple). https://pub.dartlang.org/packages/built_value (this is more up to date) https://pub.dartlang.org/packages/enums void main() { print(MyEnum.nr1.index); // prints 0 print(MyEnum.nr1.toString()); // prints nr1 print(MyEnum.valueOf("nr1").index); // prints 0 print(MyEnum.values[1].toString()) // prints nr2 print(MyEnum.values.last.index) // prints 2 print(MyEnum.values.last.myValue); // prints 15 }
Here is the function that converts given string to enum type: EnumType enumTypeFromString(String typeString) => EnumType.values .firstWhere((type) => type.toString() == "EnumType." + typeString); And here is how you convert given enum type to string: String enumTypeToString(EnumType type) => type.toString().split(".")[1];
Generalizing on #Pedro Sousa's excellent solution, and using the built-in describeEnum function: extension StringExtension on String { T toEnum<T extends Object>(List<T> values) { return values.singleWhere((v) => this.equalsIgnoreCase(describeEnum(v))); } } Usage: enum TestEnum { none, test1, test2 } final testEnum = "test1".toEnum(TestEnum.values); expect(testEnum, TestEnum.test1);
import 'package:collection/collection.dart'; enum Page { login, profile, contact, } Widget page(String key){ Page? link = Page.values.firstWhereOrNull((e) => e.toString().split('.').last == key); switch (link) { case Page.login: return LoginView(); case Page.profile: return const ProfileView(); case Page.contact: return const ContactView(); default: return const Empty(); } }
#Collin Jackson has a very good answer IMO. I had used a for-in loop to achieve a similar result prior to finding this question. I am definitely switching to using the firstWhere method. Expanding on his answer this is what I did to deal with removing the type from the value strings: enum Fruit { apple, banana } class EnumUtil { static T fromStringEnum<T>(Iterable<T> values, String stringType) { return values.firstWhere( (f)=> "${f.toString().substring(f.toString().indexOf('.')+1)}".toString() == stringType, orElse: () => null); } } main() { Fruit result = EnumUtil.fromStringEnum(Fruit.values, "apple"); assert(result == Fruit.apple); } Maybe someone will find this useful...
I had the same problem in one of my projects and existing solutions were not very clean and it didn't support advanced features like json serialization/deserialization. Flutter natively doesn't currently support enum with values, however, I managed to develop a helper package Vnum using class and reflectors implementation to overcome this issue. Address to the repository: https://github.com/AmirKamali/Flutter_Vnum To answer your problem using Vnum, you could implement your code as below: #VnumDefinition class Visibility extends Vnum<String> { static const VISIBLE = const Visibility.define("VISIBLE"); static const COLLAPSED = const Visibility.define("COLLAPSED"); static const HIDDEN = const Visibility.define("HIDDEN"); const Visibility.define(String fromValue) : super.define(fromValue); factory Visibility(String value) => Vnum.fromValue(value,Visibility); } You can use it like : var visibility = Visibility('COLLAPSED'); print(visibility.value); There's more documentation in the github repo, hope it helps you out.
When migrating to null-safety, the Iterable.firstWhere method no longer accepts orElse: () => null. Here is the implementation considering the null-safety: import 'package:collection/collection.dart'; String enumToString(Object o) => o.toString().split('.').last; T? enumFromString<T>(String key, List<T> values) => values.firstWhereOrNull((v) => key == enumToString(v!));
enum Fruit { orange, apple } // Waiting for Dart static extensions // Issue https://github.com/dart-lang/language/issues/723 // So we will be able to Fruit.parse(...) extension Fruits on Fruit { static Fruit? parse(String raw) { return Fruit.values .firstWhere((v) => v.asString() == raw, orElse: null); } String asString() { return this.toString().split(".").last; } } ... final fruit = Fruits.parse("orange"); // To enum final value = fruit.asString(); // To string
I think my approach is slightly different, but might be more convenient in some cases. Finally, we have parse and tryParse for enum types: import 'dart:mirrors'; class Enum { static T parse<T>(String value) { final T result = (reflectType(T) as ClassMirror).getField(#values) .reflectee.firstWhere((v)=>v.toString().split('.').last.toLowerCase() == value.toLowerCase()) as T; return result; } static T tryParse<T>(String value, { T defaultValue }) { T result = defaultValue; try { result = parse<T>(value); } catch(e){ print(e); } return result; } } EDIT: this approach is NOT working in the Flutter applications, by default mirrors are blocked in the Flutter because it causes the generated packages to be very large.
enum in Dart just has too many limitations. The extension method could add methods to the instances, but not static methods. I really wanted to be able to do something like MyType.parse(myString), so eventually resolved to use manually defined classes instead of enums. With some wiring, it is almost functionally equivalent to enum but could be modified more easily. class OrderType { final String string; const OrderType._(this.string); static const delivery = OrderType._('delivery'); static const pickup = OrderType._('pickup'); static const values = [delivery, pickup]; static OrderType parse(String value) { switch (value) { case 'delivery': return OrderType.delivery; break; case 'pickup': return OrderType.pickup; break; default: print('got error, invalid order type $value'); return null; } } #override String toString() { return 'OrderType.$string'; } } // parse from string final OrderType type = OrderType.parse('delivery'); assert(type == OrderType.delivery); assert(type.string == 'delivery');
another variant, how it might be solved: enum MyEnum { value1, value2, } extension MyEnumX on MyEnum { String get asString { switch (this) { case MyEnum.value1: return _keyValue1; case MyEnum.value2: return _keyValue2; } throw Exception("unsupported type"); } MyEnum fromString(String string) { switch (string) { case _keyValue1: return MyEnum.value1; case _keyValue2: return MyEnum.value2; } throw Exception("unsupported type"); } } const String _keyValue1 = "value1"; const String _keyValue2 = "value2"; void main() { String string = MyEnum.value1.asString; MyEnum myEnum = MyEnum.value1.fromString(string); }
enum HttpMethod { Connect, Delete, Get, Head, Options, Patch, Post, Put, Trace } HttpMethod httpMethodFromString({#required String httpMethodName}) { assert(httpMethodName != null); if (httpMethodName is! String || httpMethodName.isEmpty) { return null; } return HttpMethod.values.firstWhere( (e) => e.toString() == httpMethodName, orElse: () => null, ); }
You can do something like this: extension LanguagePreferenceForString on String { LanguagePreferenceEntity toLanguagePrerence() { switch (this) { case "english": return LanguagePreferenceEntity.english; case "turkish": return LanguagePreferenceEntity.turkish; default: return LanguagePreferenceEntity.english; } } }
should returning const value from const object be const?
I'm using the style class below to mimick enums (from Does Dart support enumerations?) It is working fine in that this snippet produces expected results. void main() { InterpolationType it = InterpolationType.LINEAR; print("it is $it and stringified ${stringify(it)}"); print(InterpolationType.fromJson(it.toJson())); } But the DartEditor is complaining about "Expected constant expression" in the case statements of fromJson method. Is there a const I can throw in somewhere to get rid of this complaint? class InterpolationType { static const LINEAR = const InterpolationType._(0); static const STEP = const InterpolationType._(1); static const CUBIC = const InterpolationType._(2); static get values => [ LINEAR, STEP, CUBIC ]; final int value; const InterpolationType._(this.value); String toString() { switch(this) { case LINEAR: return "LINEAR"; case STEP: return "STEP"; case CUBIC: return "CUBIC"; } } int toJson() { return this.value; } static InterpolationType fromJson(int v) { switch(v) { case LINEAR.value: return LINEAR; case STEP.value: return STEP; case CUBIC.value: return CUBIC; } } static InterpolationType fromString(String s) { switch(s) { case "LINEAR": return LINEAR; case "STEP": return STEP; case "CUBIC": return CUBIC; } } }
As you discovered: accessing fields from a const object is not a constant operation. So the editor (as well as the VM and dart2js) are right. With the current syntax there is no way to express a (informal) contract that a field of a class will always be a final field. For example, I could change the value-field to be a getter instead of a field. The interface-contract of the class definitely allows me to do that, because I never told anybody that I would keep "value" as a field. However if I did that it would break every program that relied on the existence of this final field. As a consequence the current behavior is very unlikely to change. However: in theory it would be possible to improve the Dart language so that you could use "const" instead of "final" for local fields, and initialize them with initializer lists. And in this case accessing the field could be considered a constant operation. I currently don't see any downsides to this behavior and it would be backwards-compatible. // WARNING: What follows DOES NOT WORK, just a potential example class InterpolationType { const value; // Note the "const" instead of "final". const InterpolationType._(this.value); } The language is already pretty stable but you can open a bug at http://dartbug.com/ and suggest this behavior. It's not very likely that the feature-request would be accepted, but it's definitely worth a try.
Unit test hanging when using lexical scoping and generics with extends
The behavior seems to be related to the presence of 'extends' as shown with unit test below: typedef dynamic GetFromThing<T extends Thing>(T target); typedef GetFromThing<T> DefGetFromThing<T extends Thing>(dynamic def); typedef dynamic GetFromT<T>(T target); typedef GetFromT<T> DefGetFromT<T>(dynamic def); class Thing { int value; } class Test { static final GetFromThing<Thing> fromThingSimple = (Thing target) { return target.value; }; static final DefGetFromThing<Thing> fromThing = (dynamic def) { return (target) => null; }; static final DefGetFromT<int> fromInt = (dynamic def) { return (target) => null; }; } main() { test('this works', () { var temp1 = Test.fromThingSimple(new Thing()); }); test('this works too', () { var temp = Test.fromInt(10); }); test('should let me call lexically closed functions', () { var temp = Test.fromThing(10); // <-- causes test to hang }); }
The fact that the VM hangs is clearly a bug. The code is legal. The fact that typedefs describe function types and can be generic whereas function types themselves are never generic is not an issue in principle (though it might be for the implementation).
I find it very interesting that type parameters in typedefs work without some kind of warning or error, since Dart doesn't have generic methods. You very well may have come across two bugs here, the first that there's no errors, and the second that the VM hangs.