Last updated:
0 purchases
enum assist
Enum Assist
Seamlessly generate extension methods and json conversion classes for your enums!
Motivation #
Dart enums can be a bit tedious to work with. Serializing them to/from json, using
switch statements based off their values, or using describeEnum or split('.') to get the value's name are a couple of examples where working with enums could be improved.
Writing extensions has been a great way to add extra functionality to classes & enums. Though, you'll find yourself writing the same extensions over and over again. I was getting tired of copying and pasting code and changing a couple of things whenever I created a new enum. So I did what any other sane developer would do, I took a couple of weeks to create an automation tool to save me time. 🤪
So welcome enum_assist into your lifeproject! The fastest way to writing extension methods and json conversion classes for your enums!
Check out the example or the index to see what it can do.
Index #
Motivation
Index
How to use
Install
Generating the Code
Build Runner Commands
File Setup
Features
Default Extension Methods
map/maybeMap
Return Type
Custom Extensions
Map Extension
Maybe Extension
Json Converter
Build Configuration
Examples
Map Example
MaybeMap Example
toJson & fromJson
Using json_serializable
Manually Serializing
How to use #
Install #
To use enum_assist, you will need to set up the build_runner (code-generator) for your project.
First, add the following packages to your pubspec.yaml:
depenedencies:
enum_assist_annotation:
dev_dependencies:
build_runner:
enum_assist:
copied to clipboard
What are these packages?
enum_assist_annotation, contains all the annotations that enum_assist references to generate code.
Why a Dependency: The annotations are a part of your code, so enum_assist_annotation
must be part of the dependencies
enum_assist, the code generator for all the annotations
Why a Dev Dependency?: The generator only generates the code. So its technically not part of your code which means that enum_assist can be part of the dev_dependencies
build_runner, a tool that any code-generator package uses to generate code
Why a Dev Dependency?: Same reason as enum_assist
Generating the Code #
Build Runner Commands #
// 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
copied to clipboard
If you're new to build_runner, I suggest taking a look at these commands & options
File Setup #
Each file will need to start with the enum_assist import and the part keyword.
import 'package:enum_assist_annotation/enum_assist_annotation.dart';
part 'example.ge.dart';
copied to clipboard
Features #
Default Extension Methods #
The following methods will be generated with every enum annotated with EnumAssist
Name
The name of the enum value.
Greeting.friendly.name; // friendly
copied to clipboard
Description
Greeting.friendly.description; // A friendly greeting
copied to clipboard
To Int
Greeting.professional.toInt; // 0
Greeting.friendly.toInt; // 1
Greeting.relaxed.toInt; //2
copied to clipboard
Readable
Greeting.friendly.readable; // Friendly
copied to clipboard
Serialized
Specific case formatting can be done with serializedFormat (either EnumAssist or build.yaml)
Greeting.friendly.serialized; // friendly
copied to clipboard
map/maybeMap #
The base of all custom extension methods.
Each enum will generate a .map(...) & .maybeMap(...) method, which is equivalent to pattern matching.
.map() provides callbacks for each enum value. All callbacks are required args and can return any value.
var greet = Greeting.friendly;
final whatDoYouSay = greet.map(
professional: 'Hello Sir',
friendly: 'Hello',
relaxed: 'Saaa dude!',
);
whatDoYouSay; // Hello
copied to clipboard
.maybeMap() provides callbacks for each enum value, plus an orElse callback.
orElse is the only required arg.
var greet = Greeting.friendly;
final whatDoYouSay = greet.maybeMap(
professional: 'Hello Sir',
orElse: '*blank stare*',
);
whatDoYouSay; // *blank stare*
copied to clipboard
Return Type #
.map<T>() and .maybeMap<T>() use generics to provide the return type of the callback.
var greet = Greeting.friendly;
final whatDoYouSay = greet.map<String>(
professional: 'Hello Sir',
friendly: 'Hello',
// relaxed: 123, // compile error: `123` is not a type String
relaxed: 'Saaa dude!',
);
whatDoYouSay.runtimeType; // String
copied to clipboard
While its not necessary to define the return type, it is recommended to do so.
var greet = Greeting.friendly;
final whatDoYouSay = greet.map(
professional: 'Hello Sir',
friendly: 'Hello',
relaxed: 'Saaa dude!',
);
whatDoYouSay.runtimeType; // String
copied to clipboard
var greet = Greeting.friendly;
final whatDoYouSay = greet.map(
professional: 'Hello Sir',
friendly: null,
relaxed: 'Saaa dude!',
);
whatDoYouSay.runtimeType; // String?
copied to clipboard
var greet = Greeting.friendly;
final whatDoYouSay = greet.map(
professional: 'Hello Sir',
friendly: null,
relaxed: 3,
);
whatDoYouSay.runtimeType; // Object?
copied to clipboard
Custom Extensions #
enum_assist allows you to create your own extension methods for your enum
There are two ways to create your own extension methods.
You start by creating a class that extends MapExtension or MaybeExtension.
IMPORTANT: All properties (other than value) need to be defined
within the super constructor!
Map Extension #
class SaidByExt extends MapExtension<String> {
const SaidByExt(String value)
: super(
value,
methodName: 'saidBy',
docComment: 'Greeting that is said by...',
allowNulls: false, // default
);
}
copied to clipboard
Add it to the the extensions property
@EnumAssist()
enum Greeting {
@EnumValue(extensions: [SaidByExt('Executives')])
professional,
@EnumValue(extensions: [SaidByExt('Co-workers')])
friendly,
@EnumValue(extensions: [SaidByExt('Friends')])
relaxed,
}
copied to clipboard
Generated code:
/// Greeting that is said by...
String get saidBy {
return map<String>(
professional: 'Executives',
friendly: 'Co-workers',
relaxed: 'Friends',
);
}
copied to clipboard
Maybe Extension #
Expected return values:
defaultValue:
When the extension value is not defined
When the extension value is null AND allowNulls is false
null
When the extension value is null AND allowNulls is true
class HowFrequentExt extends MaybeExtension<int?> {
const HowFrequentExt(int? value)
: super(
value,
methodName: 'howFrequent',
docComment: '1-10, How often this greeting is used.\n\n`null` if never used',
defaultValue: 0,
allowNulls: true,
);
}
copied to clipboard
Add it to the the extensions property
@EnumAssist()
enum Greeting {
@EnumValue(extensions: [])
professional,
@EnumValue(extensions: [HowFrequentExt(3)])
friendly,
@EnumValue(extensions: [HowFrequentExt(8)])
relaxed,
@EnumValue(extensions: [HowFrequentExt(null)])
rude,
}
copied to clipboard
Generated Code:
/// 1-10, How often this greeting is used
///
/// `null` if never used
int? get howFrequent {
return maybeMap<int?>(
// returns default value
//? if theres an argument provided, it does nothing.
orElse: HowFrequentExt(3).defaultValue,
professional: HowFrequentExt(3).defaultValue,
friendly: 3,
relaxed: 8,
rude: null,
);
}
copied to clipboard
Notice This:
For the generated code to access the defaultValue, it must create an instance of the
extension class. If there is a required argument, the arg must be passed to avoid
exceptions. Therefore, the required arg will be provided & ignored to return default value.
Note:
If an extension is omitted (like professional), the default value will be used.
null will only be returned if declared with a null value. (like rude)
Json Converter #
Serializing enums almost always requires a switch statement.
Mistakes can easily be made when converting from a String (or other types) to an enum.
The Json converter class is a great way to handle your enums' serialization.
The name of json converter class will be ${NAME_OF_ENUM}Conv
For a detailed example, go to toJson/fromJson
// Generated Json Converter class
final conv = GreetingConv();
conv.toJson(Greeting.professional); // professional
conv.fromJson('professional'); // Greeting.professional
copied to clipboard
Build Configuration #
Customize the settings for each enum, or for all enums inside your build.yaml file.
targets:
$default:
builders:
enum_assist:
enabled: true
options:
# possible values:
# - true
# - false
# default: true
create_json_conv: true
create_name: true
create_description: true
create_to_int: true
create_readable: true
create_serialized: true
# possible values:
# - camel
# - capital
# - constant
# - dot
# - header
# - kebab
# - no
# - none
# - pascal
# - path
# - sentence
# - snake
# - swap
# default: none
serialized_format: none
# possible values:
# - true
# - false
# default: true
use_doc_comment_as_description: true
# possible values:
# - true
# - false
# default: false
use_int_value_for_serialization: false
copied to clipboard
Some Examples!
Examples #
Map Example #
Lets create a .response method for the enum Greeting enum.
This method will return a String with the response to the greeting.
We first need to create our extension class.
class ResponseExt extends MapExtension<String> {
const ResponseExt(String value)
: super(
value,
methodName: 'response',
docComment: 'The response to a greeting',
);
}
copied to clipboard
Note:
The MapExtension class also has an allowNulls argument, which defaults to false.
This can be set to true to change the return type nullable.
Next, we need to add our extension to the enum.
This can be done by annotating the enum's values with EnumValue,
And then adding the extension to the extensions field.
Note:
Because the .map(...) requires all args to be defined, we must add the ResponseExt extension to ALL enum fields.
Failure to do so will result in an error when generating the code.
@EnumAssist()
enum Greeting {
@EnumValue(
extensions: [
ResponseExt('Hello, how do you do?'),
],
)
professional,
@EnumValue(
extensions: [
ResponseExt('Hey! Hows it going?'),
],
)
friendly,
@EnumValue(
extensions: [
ResponseExt('Whats up my dude!?'),
],
)
relaxed,
}
copied to clipboard
After the build_runner has run, you can now access the .response method on the Greeting enum.
var greet = Greeting.friendly;
greet.response; // Hey! Hows it going?
Greeting.relaxed.response; // Whats up my dude!?
copied to clipboard
MaybeMap Example #
Lets create a .isCool method for the Greeting enum.
This method will return true only if the enum value is friendly or relaxed. Or else it will return false.
We first need to create our extension class.
class IsCoolExt extends MaybeExtension<bool> {
const IsCoolExt(bool value)
: super(
value,
methodName: 'isCool',
defaultValue: false,
docComment: 'Is this a cool greeting?',
);
}
copied to clipboard
Note:
The MaybeExtension class also has an allowNulls argument, which defaults to false.
This can be set to true if you want the return type to be nullable.
Note:
The constructor could take a named argument with a default value to reduce the amount of code needed.
const IsCoolExt([bool value = true])
copied to clipboard
Next, we need to add our extension to the enum.
This can be done by adding the EnumValue annotation to any enum field.
And then adding the extension to the extensions list.
@EnumAssist()
enum Greeting {
professional,
@EnumValue(
extensions: [
IsCoolExt(true),
],
)
friendly,
@EnumValue(
extensions: [
IsCoolExt(true),
],
)
relaxed,
}
copied to clipboard
Notice This:
We did not annotate professional with EnumValue or IsCoolExt.
This is because .maybeMap(...) doesn't require all callbacks to be defined.
The generator will use the defaultValue from IsCoolExt as the return value.
After the build_runner has run, you can now access the .isCool method on the Greeting enum.
var greet = Greeting.friendly;
greet.isCool; // true
Greeting.professional.isCool; // false
copied to clipboard
toJson & fromJson #
Serializing enums almost always requires a switch statement.
Mistakes can easily be made when converting from a string to an enum.
Json Converter Classes are a great way to handle this.
Let's create an enum for the two examples below, Using json_serializable & Manually Serializing.
@EnumAssist()
enum SuperHeroes {
@EnumValue(serializedValue: 'Capitan America')
capitanAmerica,
@EnumValue(serializedValue: 'Black Widow')
blackWidow,
@EnumValue(serializedValue: 'Dr. Strange')
drStrange,
}
copied to clipboard
Using json_serializable #
json_serializable will automatically serialize enums to json by using describeEnum. This is great if your enum's values are exactly the same as the json values. But that is not always the case, just like our SuperHeroes enum.
Let's use our generated class SuperHeroesConv fix that problem!
Here is our example class, annotated with JsonSerializable
@JsonSerializable()
class Character {
const Character(
this.secretIdentity,
this.hero,
this.powerLevel,
);
final String secretIdentity;
final SuperHeroes hero;
factory Character.fromJson(Map<String, dynamic> json) =>
_$CharacterFromJson(json);
}
copied to clipboard
By default, json_serializable will serialize our hero field using the literal enum value as a String.
final steve = Character('Steve Rogers', SuperHeroes.capitanAmerica);
final json = steve.toJson();
print(json['hero']); //capitanAmerica
copied to clipboard
To tell json_serializable to convert the hero field with the values provided by EnumValue.serializedValue, you'll need to annotated the field in your class
final String secretIdentity;
@SuperHeroesConv()
final SuperHeroes hero;
copied to clipboard
Note:
If hero were nullable, you would need to annotate the field with a nullable converter
final String secretIdentity;
@SuperHeroesConv.nullable
final SuperHeroes hero;
copied to clipboard
After you run the build_runner, the json value for the hero field will now be
final steve = Character('Steve Rogers', SuperHeroes.capitanAmerica);
final json = steve.toJson();
print(json['hero']); // Capitan America
copied to clipboard
Manually Serializing #
Here is an example of what your class could look like
class Character {
const Character(
this.secretIdentity,
this.hero,
);
final String secretIdentity;
final SuperHeroes hero;
Map<String, dynamic> toJson() {
return {
'secretIdentity': secretIdentity,
'hero': superHeroToJson(hero),
};
}
factory Character.fromJson(Map<String, dynamic> json) {
return Character(
json['secretIdentity'] as String,
superHeroFromJson(json['hero'] as String),
);
}
}
String superHeroToJson(SuperHeroes hero) {
switch (hero) {
case SuperHeroes.capitanAmerica:
return 'Capitan America';
case SuperHeroes.blackWidow:
return 'Black Widow';
case SuperHeroes.drStrange:
return 'Dr. Strange';
}
}
SuperHeroes superHeroFromJson(String hero) {
switch (hero) {
case 'Capitan America':
return SuperHeroes.capitanAmerica;
case 'Black Widow':
return SuperHeroes.blackWidow;
case 'Dr. Strange':
return SuperHeroes.drStrange;
default:
throw Exception('Could not find superhero for $hero');
}
}
copied to clipboard
It's a lot of work to just convert an enum to json!
Thankfully, the generated class SuperHeroConv can do all of the work here
Our toJson and fromJson methods will now look like this
class Character {
const Character(
this.secretIdentity,
this.hero,
);
final String secretIdentity;
final SuperHeroes hero;
static const _conv = SuperHeroesConv();
Map<String, dynamic> toJson() {
return {
'secretIdentity': secretIdentity,
'hero': _conv.toJson(hero),
// you could also use the `serialized` method here
// which is the same as _conv.toJson(hero)
//
// 'hero': hero.serialized,
};
}
factory Character.fromJson(Map<String, dynamic> json) {
return Character(
json['secretIdentity'] as String,
_conv.fromJson(json['hero'] as String),
);
}
}
copied to clipboard
Here's what the hero's value would look like
final steve = Character('Steve Rogers', SuperHeroes.capitanAmerica);
final json = steve.toJson();
print(json['hero']); // Capitan America
copied to clipboard
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.