generic_enum 0.0.6

  • Readme
  • Changelog
  • Example
  • Installing
  • new56

Generic Enumeration Classes for Dart #

Build Status

Introduction #

Enumerations are ideal when we want to model choosing from a limited set of constant values. In Dart, the value of an enum resolves to a String.

GenericEnum is a base class for creating generic classes with a fixed set of static constant instances. These classes appear to the user of the library much like a Dart enum would. For example, generic enums can be used in switch statements, to initialize variables, or as default parameters in functions and constructors.

Boilerplate #

To create a generic enum class, say DpiResolution, follow these steps:

  1. Extend GenericEnum<T> where T is a Dart built-in type or a class with a const constructor.
  2. Define a private const constructor that calls the super constructor and passes on the value of type T.
  3. Define the static const instances of DpiResolution. You may capitalize instance names to mark them as constants.
  4. Optionally: Create a static final field of type BuiltMap<T,EnumClass>to provide easy access to all values and instances.
import 'package:built_collection/generic_enum.dart';
import 'package:generic_enum/generic_enum.dart';


//   1. Extend GenericEnum<T>
class DpiResolution extends GenericEnum<int> {

  // 2. Define a private const constructor that calls the super constructor
  //    and passes on the value of type int.
  const DpiResolution._(int) : super(value);

  // 3. Define static constant instances of type DpiResolution
  static const DpiResolution LOW = DpiResolution._(90);
  static const DpiResolution MEDIUM = DpiResolution._(300);
  static const DpiResolution HIGH = DpiResolution._(600);

  // 4. Define a private static field mapping each value to its instance.
  static final valueMap = BuiltMap<String,DpiResolution>({
    90: LOW,
    300: MEDIUM,
    600: HIGH,
  });
}

In principle, the creation of the valueMap, and additional methods for serialization could be done with a source code generator (see package built_value). At least for simple value types (the most common use-case) it is straightforward to serialize the value and retrieve the GenericEnum instance via the valueMap.

Usage #

GenericEnum instances and their value are compile-time constants and can be used in switch statements to initalize other constants, final variables, or as parameters or default parameters in constructors and functions. See example.dart.

The sample class ScannerSettings (defined below) illustrates the use of a generic enum.

The value of generic enums can be accessed directly using dot notation (like in the initializer statement below).

class ScannerSettings{

  const ScannerSettings({
    this.scanMode,
    this.size,
    this.dpiResolution = DpiResolution.Medium,
    },
  ):_dipRes = dpiResolution.value;

  final DpiResolution dpiResolution;
  final int _dpiRes;
  final ScanMode;
  final ScanSize;
}

Generic Enums as Annotations #

GenericEnum classes have a constant constructor and as such can be used as annotations. Annotations are commonly found in source code generating libraries.

Since generic enums are normal classes they can contain methods and final fields in addition to the value field.

As an example, we could generate an annotation class, say Constraint, that helps users select a supported Sqlite constraint.

import 'package:generic_enum/generic_enum.dart';

class Constraint extends GenericEnum<String> {
  const Constraint._(String value) : super(value);

  static const Constraint NOT_NULL = Constraint._('NOT NULL');
  static const Constraint PRIMARY_KEY = Constraint._('PRIMARY KEY');
  static const Constraint UNIQUE = Constraint._('UNIQUE');

  static final  valueMap = BuiltMap<String, Constraint>({
    'NOT NULL': NOT_NULL,
    'PRIMARY KEY': PRIMARY_KEY,
    'UNIQUE': UNIQUE,
  });

  bool get isPrimary => (this == PRIMARY_KEY);
  bool get isUnique => (this == UNIQUE);
  bool get isNotNull => (this == NOT_NULL);
}

The Constraint class could be used to annotate a field in the data class User.

import 'constraint.dart';
import 'table.dart';

@Table
class User{
  const User({@required this.id, @required this.userName});

  // Use generic enum as annotation
  @Constraint.PRIMARY_KEY
  final int id;

  @Constraint.NOT_NULL
  final String userName;
}

Retrieving Annotations of Type Generic Enum #

Using the package analyzer, the data model User can be traversed with the help of a SimpleElementVisitor.

When processing annotations (for example during source code generation: see method _addConstraint below), the recommended way of retrieving an annotation of type GenericEnum is via source_gen's ConstantReader.

This is because the generic enum value field is located in the parent class and ConstantReader searches parent classes if a field is not found in the current class.

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:sqlite_entity/sqlite_entity.dart';
import 'package:source_gen/source_gen.dart' show TypeChecker, ConstantReader, ;
import 'package:sqlite_generator/src/type_utils.dart';

/// Sample visitor class used to traverse classed annotated with @Table
class TableVisitor extends SimpleElementVisitor {

  static var constraintChecker = TypeChecker.fromRuntime(Constraint);

  List<FieldElement> fields = [];
  Map<String,Constraint> constraints = {};

  /// Mapping field name to constraint.
  Map<String,List<Constraint>> constraints = {};

  @override
  visitFieldElement(FieldElement element) {
    fields.add(element);
    _addConstraint(element);
  }

  _addConstraint(FieldElement element){
    var annotation = element.metadata;

    if (annotation == null) return;

    // Check if annotation is of type Constraint.
    if (!_constraintChecker.isAssignableFromType(annotation.type)) return;

    // Read value of generic enum.
    // Note: ConstantReader searches for the field name 'value' in a
    // super class if it is not found in the annotation class.
    String annotationValue = ConstantReader(
      annotation.computeConstantValue(),
    ).read('value').stringValue;

    // Retrieve the Constraint instance.
    var constraintInstance = Constraint.valueMap[annotationValue];
    this.constraints.add({element.name: constraintInstance});
  }
}

For more information about source code generation see: analyzer and source_gen.

Examples #

For a simple example on how to create and use a generic enum see: example.dart.

Features and bugs #

Please file feature requests and bugs at the issue tracker.

0.0.1 #

Initial Version of the library.

0.0.2 #

Library now depends on built_collection.

0.0.3 #

Changed GeneralizedEnum to GenericEnum.

0.0.4 #

Amended library description.

0.0.5 #

Amended sample code in README.md.

0.0.6 #

Shortened library description.

example/example.dart

import 'package:built_collection/built_collection.dart';
import 'package:generic_enum/generic_enum.dart';

/// Creating a GenericEnum class
///
///   1) Extend class:
class NamePart extends GenericEnum<String> {
  /// 2) Add a private const constructor:
  const NamePart._(String value) : super(value);

  /// 3) Add static const instances:
  static const NamePart FIRST_NAME = NamePart._('FIRST_NAME');
  static const NamePart LAST_NAME = NamePart._('LAST_NAME');

  /// 4) Add a private static const value map:
  static final valueMap = BuiltMap<String, NamePart>({
    'FIRST_NAME': FIRST_NAME,
    'LAST_NAME': LAST_NAME,
  });
}

/// Sample Data Class
class User {
  const User({this.firstName, this.lastName});

  final String firstName;
  final String lastName;

  @override
  String toString() {
    return '$firstName $lastName';
  }
}

/// Sample Class using a GenericEnum as a final variable
class SortableUserList {
  SortableUserList({
    List<User> users,
    // Using a GenericEnum as default parameter.
    this.sortBy = NamePart.FIRST_NAME,
  }) : this.users = List<User>.from(users) {
    /// Using a GenericEnum in a switch statement.
    switch (sortBy) {
      case NamePart.FIRST_NAME:
        this.users.sort((
              user1,
              user2,
            ) =>
                user1.firstName.compareTo(
                  user2.firstName,
                ));
        break;
      case NamePart.LAST_NAME:
        this.users.sort((
              user1,
              user2,
            ) =>
                user1.lastName.compareTo(
                  user2.lastName,
                ));
        break;
      default:
    }
  }

  List<User> users;
  final NamePart sortBy;

  @override
  String toString() {
    var buffer = StringBuffer();
    buffer.writeAll(users, ', ');
    return buffer.toString();
  }
}

/// To run this short program enter the following command followed by enter:
///
/// # dart example.dart
///
main(List<String> args) {
  final tom = User(firstName: 'Thomas', lastName: 'Fisher');
  final silvy = User(firstName: 'Silvia', lastName: 'Williams');
  final amy = User(firstName: 'Amelia', lastName: 'King');
  final john = User(firstName: 'John', lastName: 'Rowlands');

  final userList = [silvy, amy, john, tom];

  /// Using a generic enum as a constructor parameter.
  final firstUserList =
      SortableUserList(users: userList, sortBy: NamePart.FIRST_NAME);

  /// Using a generic enum as a constructor parameter.
  final secondUserList =
      SortableUserList(users: userList, sortBy: NamePart.LAST_NAME);

  /// Retrieving the GenericEnum instance from its value:
  NamePart namePart = NamePart.valueMap['FIRST_NAME'];
  assert(namePart == NamePart.FIRST_NAME);

  /// Printing the value of a generic enum.
  print('User list sorted by: ${firstUserList.sortBy}');
  print(firstUserList.toString());
  print('');

  print('User list sorted by: ${secondUserList.sortBy}');
  print(secondUserList.toString());
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  generic_enum: ^0.0.6

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:generic_enum/generic_enum.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
16
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
56
Learn more about scoring.

We analyzed this package on Jan 19, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.0
  • pana: 0.13.4

Maintenance suggestions

Package is pre-v0.1 release. (-10 points)

While nothing is inherently wrong with versions of 0.0.*, it might mean that the author is still experimenting with the general direction of the API.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.3.0 <3.0.0
built_collection ^4.3.2 4.3.2
Transitive dependencies
collection 1.14.12
matcher 0.12.6
meta 1.1.8
path 1.6.4
quiver 2.1.2+1
stack_trace 1.9.3
Dev dependencies
test ^1.11.0