Skip to content

Instantly share code, notes, and snippets.

@funder7
Last active January 8, 2021 00:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save funder7/fc37c9633c20d15cbebd1003815cf3ca to your computer and use it in GitHub Desktop.
Save funder7/fc37c9633c20d15cbebd1003815cf3ca to your computer and use it in GitHub Desktop.
Dart: Map all values of an Enum to another List of objects (uses "logger: ^0.9.4")

Dart - Enum to Object mapper - helper class

With practical example

Introduction

Enums are a good way to represent a determined set of values, which I use often. They're good when used in code, or when matching some values returned from your backend APIs with the client code for example.

But moving on the UI, you cannot present them as-is to the end user, they must be converted into something more readable.

How it works

In dart, you can use Enum.values, which returns a list of all the Keys registered. You can also define Maps, having an Enum used as key, and any object as value.

This is exactly how this code works. It was more an excercise for checking my dev skills to be honest, I started to use Dart less than 3 weeks ago, so please forgive any error (suggestions are welcome!) (:)

Creating a new mapper

The example shows how to map a String, or a Color, but you're free to create your own, it's just a matter of writing some lines of code.

  1. Create the new class, and define which object will be mapped, now it's a int:

abstract class EnumIntegerMapper<Enum> extends EnumObjectMapper<Enum, int> {

  1. (optional) If you need to, update the validation condition:
@override
  bool invalidCondition(int value) => super.invalidCondition(value) || value < 0;

Note: You're not obliged to call the super method, in this case we are mapping an integer, which cannot be null (super.invalidCondition is a null check), so it would be better to do something like that:

@override
  bool invalidCondition(int value) => value < 0 || value > 10;

The type of value will be equal to the mapped value's type, remember to update your function in case of overriding!

  1. (optional) Update the error message, for validation failures:
@override
  String validationError(Enum key, int value) => 'Number for key: $key is out of range ($value)!';
  1. Create another class, to define which Enum will be mapped to values:
class MyEnumToIntegerMapper extends EnumIntegerMapper<MyEnum, int> {

Usage

You are ready to use the new helpers. As you can imagine, two methods are available. I'm using the classes defined in the other files here:

  String orderStatus = OrderStatusLabelMapper().getValue(OrderStatus.INITIALIZED);
  
  // orderStatus = 'Initialized';

  OrderStatus inverseValue = OrderStatusLabelMapper().getKey(orderStatus);

  // inverseValue = OrderStatus.INITIALIZED;

What can be done next

Eventually, the code can be made even more generic, in order to accept any kind of object, not only an Enum as input. But let's stop here for the moment.

That's all Folks

I hope that this code may have helped you, probably it can be improved, but also adapted for other usages. I think that it's a good starting point to understand how inheritance works.

Maybe with a mixin, more functionalities can be added, for example another validation rule, or a pre-processing function to transform the input values before they're mapped (grouping?). It's up to your fantasy! ;)

.. Dart is a nice language!

Happy coding! ;)

import 'package:logger/logger.dart';
/// Utility class which defines methods for mapping all enum
/// values to another set of objects, doing some validation
/// on the input elements.
/// This class has some default validation rules which can
/// be overridden in child classes (see next file)
abstract class EnumObjectMapper<Enum, O> {
// The enum values
final List<Enum> values;
// Map of Enum value -> Object representation
final Map<Enum, O> objectMappings;
//
final Logger log = Logger();
EnumObjectMapper(this.values, {this.objectMappings})
: assert(values != null, 'Values must be set'),
assert(values.isNotEmpty, 'Values cannot be empty'),
assert(objectMappings != null, 'Mappings must be set'),
assert(objectMappings.isNotEmpty, 'Mappings cannot be empty') {
objectMappings.forEach((key, value) {
assert(values.contains(key),
'Mapping "$key" doesn\'t exist in input ${E.runtimeType} enum');
if (invalidCondition(value)) {
log.w('ATTENTION - ${validationError(key, value)}');
// Without Logger
// debugPrint('ATTENTION - ${validationError(key, value)}');
}
});
}
// VALIDATION
bool invalidCondition(O value) => value == null;
String validationError(Enum key, O value) => 'Value of key: $key is not set ($value)!';
// FUNCTIONS
O getValue(Enum enumKey) => objectMappings[enumKey];
Enum getKey(String value) => objectMappings.keys
.firstWhere((k) => objectMappings[k] == value, orElse: () => null);
}
import 'enum_object_mapper.dart';
/// Utility class specialized into Enum to [String] conversion, can be
/// used for converting an input enum key into a localized string for example.
/// You can see how the validation behavior is updated by overriding the
/// validation condition & the error message, in case it doesn't pass.
abstract class EnumStringMapper<Enum> extends EnumObjectMapper<Enum, String> {
EnumStringMapper(List<Enum> values, {Map<Enum, String> stringMappings})
: super(values, objectMappings: stringMappings);
// Enrich the validation rule
@override
bool invalidCondition(String value) => super.invalidCondition(value) || value.isEmpty;
// Customize the validation error
@override
String validationError(Enum key, String value) => 'Label for key: $key is not set ($value)!';
}
//
// This is the final implementation, showing how the util classes can be used
// on real objects.
//
/// [Enum] that will be mapped
enum OrderStatus {
INITIALIZED,
PAYMENT_KO,
CONFIRMED,
ERROR
}
/// Mapping each [OrderStatus] status to a [String] label
class OrderStatusLabelMapper extends EnumStringMapper<OrderStatus> {
static Map<OrderStatus, String> _mappings = {
OrderStatus.INITIALIZED : 'Initialized',
OrderStatus.PAYMENT_KO : 'Payment error',
OrderStatus.CONFIRMED : 'Confirmed',
OrderStatus.ERROR : 'Generic error',
};
OrderStatusLabelMapper() : super(OrderStatus.values, stringMappings: _mappings);
}
/// Mapping each [OrderStatus] status to another object: in this case, a [Color]
class OrderStatusColorMapper extends EnumObjectMapper<OrderStatus, Color> {
static final Color _intermediate = Colors.yellow;
static final Color _complete = Colors.green;
static final Color _problem = Colors.red;
static final Map<OrderStatus, Color> colorMappings = {
OrderStatus.INITIALIZED : _intermediate,
OrderStatus.PAYMENT_KO : _problem,
OrderStatus.CONFIRMED :_complete,
OrderStatus.ERROR : _problem,
};
OrderStatusColorMapper() : super(OrderStatus.values, objectMappings: colorMappings);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment