enum_flag 3.0.0
enum_flag: ^3.0.0 copied to clipboard
Portable enum bit flags for Dart with typed FlagSet values, stable bit positions, and int extensions.
enum_flag #
Portable, type-safe enum flags for Dart. Use the familiar int extensions or
an immutable FlagSet<T> while keeping the same unsigned 32-bit representation
on the Dart VM and JavaScript.
Features #
- Portable masks from
0through0xFFFFFFFF. - Stable explicit bit positions for databases and APIs.
- Convenient index-based flags for local, non-persisted state.
- Typed immutable
FlagSet<T>operations. - Backwards-compatible extensions on
int,int?, andIterable. - Preservation and reporting of unknown bits for forward compatibility.
- Explicit conversion to and from signed 32-bit storage.
Installation #
dependencies:
enum_flag: ^3.0.0
Defining flags #
Stable explicit positions #
Declare bitIndex explicitly when a mask is persisted or exchanged with
another application. Members can then be reordered without changing their
stored values.
import 'package:enum_flag/enum_flag.dart';
enum Permission with EnumFlag {
read(0),
write(1),
execute(2),
delete(3);
const Permission(this.bitIndex);
@override
final int bitIndex;
}
Every position must be unique and between 0 and 31. Invalid positions throw a
RangeError in every build mode, including JavaScript production builds.
Index-based positions #
For state that is never persisted, the mixin uses the declaration index by default:
enum LocalFeature with EnumFlag {
compactMode, // bitIndex: 0, value: 1
diagnostics, // bitIndex: 1, value: 2
}
If these values are persisted, never reorder existing members or insert new members before them. Only append new members, or migrate to explicit positions.
Typed FlagSet API #
Create an immutable, typed set from flags or from an unsigned mask:
final permissions = [Permission.read, Permission.execute].flagSet;
final restored = FlagSet<Permission>.fromBits(5);
print(permissions == restored); // true
print(permissions.bits); // 5
print(permissions.contains(Permission.read)); // true
print(permissions.activeFlags(Permission.values));
// [Permission.read, Permission.execute]
All updates return a new value:
final updated = permissions
.add(Permission.write)
.remove(Permission.execute)
.toggle(Permission.delete);
print(updated.bits); // 11
Bulk operations are also available:
final updated = const FlagSet<Permission>.empty()
.addAll([Permission.read, Permission.write])
.toggleAll([Permission.write, Permission.execute])
.removeAll([Permission.read]);
print(updated.bits); // 4
FlagSet<T> stores only the mask. Pass the enum's values list when an
operation needs to interpret all known members.
int API #
The v2-style extensions remain first-class APIs:
var bits = noFlags;
bits = bits.addFlag(Permission.read);
bits = bits.addFlags([Permission.write, Permission.execute]);
bits = bits.removeFlag(Permission.execute);
print(bits.hasFlag(Permission.read)); // true
print(bits.hasAnyFlag([Permission.execute, Permission.write])); // true
print(bits.hasAllFlags([Permission.read, Permission.write])); // true
print(bits.getFlags(Permission.values));
// [Permission.read, Permission.write]
Combine iterables into either representation:
final bits = [Permission.read, Permission.write].flag; // 3
final allBits = Permission.values.all; // 15
final set = [Permission.read, Permission.write].flagSet;
Nullable masks retain the v2 helpers:
int? storedBits;
storedBits.hasFlagOrFalse(Permission.read); // false
storedBits.hasAnyFlagOrFalse(Permission.values); // false
storedBits.hasAllFlagsOrFalse(Permission.values); // false
storedBits.orNoFlags(); // 0
Unknown bits #
Masks from a newer producer may contain positions the current enum does not
know. They are preserved through FlagSet operations and reported explicitly:
final permissions = FlagSet<Permission>.fromBits(0x13);
print(permissions.unknownBits(Permission.values)); // 16
print(permissions.describe(Permission.values));
// read | write | unknown(0x00000010)
The same behavior is available through int.getUnknownBits() and
int.describeFlags().
Signed 32-bit storage #
The canonical representation is unsigned. Use explicit helpers for a database column or protocol that represents the same bits as a signed 32-bit integer:
final stored = FlagSet<Permission>.fromSigned32(-1);
print(stored.bits); // 4294967295 (0xFFFFFFFF)
print(stored.signedBits); // -1
Values outside the corresponding signed or unsigned 32-bit range throw a
RangeError; they are never normalized silently.
Debug properties #
print(Permission.read.bitIndex); // 0
print(Permission.read.value); // 1
print(Permission.read.label); // read
print(Permission.read.binary); // 00000001
label always uses the enum declaration name, even if the enum overrides
toString(). binary is padded to at least eight characters and grows up to
32 characters for higher positions.
Migrating from v2 #
Most calls continue to compile. Review these intentional changes:
- Masks and receivers must be unsigned 32-bit values. Convert signed storage
with
FlagSet.fromSigned32. - A flag outside positions 0-31 now throws in release as well as debug builds.
labeluses the declaration name instead of parsingtoString().describeFlagsreports unknown bits instead of returningnonefor a non-zero unknown mask.getFlagsanddescribeFlagsnow accept anyIterable<T>.- Persisted index-based enums should override
bitIndexbefore members are reordered or inserted.
Development and coverage #
Run the same coverage gate used by CI:
dart test --coverage=coverage
dart run coverage:format_coverage \
--lcov \
--in=coverage \
--out=coverage/lcov.info \
--report-on=lib
dart run tool/check_coverage.dart
The final command fails unless every executable line under lib is covered.
License #
MIT License - see LICENSE for details.