Super Enum is now deprecated in favor of Dart's Patterns. Read more here.
Super-powered enums similar to sealed classes in Kotlin
-
PartFile naming has been changed.
- Should use
filename.super.dart
instead offilename.g.dart
- Should use
-
All
asyncWhenX
methods have been removed as they were redundant.- Use regular
whenX
methods with async callback functions instead of them
asyncWhen
->when
asyncWhenOrElse
->whenOrElse
- Use regular
- DataField signature has been changed.
DataField(String,Type)
should be replaced withDataField<Type>(String)
- Should specify generic data types (eg:
List
,Map
) in the following manner-DataField<List<Foo>>('foos')
Add the following to you pubspec.yaml
and replace [version]
with the latest version:
dependencies:
super_enum: ^[version]
dev_dependencies:
super_enum_generator: ^[version]
build_runner: ^1.7.1 // Any latest version which works with your current Dart or Flutter SDK
A Super Enum can be easily generated by annotating a private enum with @superEnum
import 'package:super_enum/super_enum.dart';
part "result.super.dart";
/// Generic Result Union
@superEnum
enum _Result {
/// Success case Of the Result
@generic
@Data(fields: [
DataField<Generic>('data'),
DataField<String>('message'),
])
Success,
/// Error case Of the Result
@object
Error,
}
@Data()
marks an enum value to be treated as a Data class.
- One should supply a list of possible fields inside the annotation.
- If you don't want to add fields, use
@object
annotation instead. - Fields are supplied in the form of
DataField
objects. - Each
DataField
must contain thename
and thetype
of the field. - If you want some
DataField
to be optional, setrequired
param tofalse
. - If the field type needs to be generic use
Generic
type and annotate the enum value with@generic
annotation.
@object
marks an enum value to be treated as an object.
The docComments
will also get copy-pasted in the generated classes.
NOTE: If you want to use existing classes directly without having them auto-generated and wrapped use
@UseClass()
.
Run the build_runner
command to generate the filename.super.dart
part file.
# Dart SDK: $pub run build_runner build
# Flutter SDK: $flutter pub run build_runner build
/// Generic Result Union
@immutable
abstract class Result<T> extends Equatable {
const Result(this._type);
/// Success case Of the Result
factory Result.success({@required T data, @required String message}) =
Success<T>.create;
/// Error case Of the Result
factory Result.error() = Error<T>.create;
final _Result _type;
/// The [when] method is the equivalent to pattern matching.
/// Its prototype depends on the _Result [_type]s defined.
R when<R extends Object>(
{@required R Function(Success<T>) success,
@required R Function() error}) {
assert(() {
if (success == null || error == null) {
throw 'check for all possible cases';
}
return true;
}());
switch (this._type) {
case _Result.Success:
return success(this as Success);
case _Result.Error:
return error();
}
}
/// The [whenOrElse] method is equivalent to [when], but doesn't require
/// all callbacks to be specified.
///
/// On the other hand, it adds an extra orElse required parameter,
/// for fallback behavior.
R whenOrElse<R extends Object>(
{R Function(Success<T>) success,
R Function() error,
@required R Function(Result<T>) orElse}) {
assert(() {
if (orElse == null) {
throw 'Missing orElse case';
}
return true;
}());
switch (this._type) {
case _Result.Success:
if (success == null) break;
return success(this as Success);
case _Result.Error:
if (error == null) break;
return error();
}
return orElse(this);
}
/// The [whenPartial] method is equivalent to [whenOrElse],
/// but non-exhaustive.
void whenPartial({void Function(Success<T>) success, void Function() error}) {
assert(() {
if (success == null && error == null) {
throw 'provide at least one branch';
}
return true;
}());
switch (this._type) {
case _Result.Success:
if (success == null) break;
return success(this as Success);
case _Result.Error:
if (error == null) break;
return error();
}
}
@override
List<Object> get props => const [];
}
/// Success case Of the Result
@immutable
abstract class Success<T> extends Result<T> {
const Success({@required this.data, @required this.message})
: super(_Result.Success);
/// Success case Of the Result
factory Success.create({@required T data, @required String message}) =
_SuccessImpl<T>;
final T data;
final String message;
/// Creates a copy of this Success but with the given fields
/// replaced with the new values.
Success<T> copyWith({T data, String message});
}
@immutable
class _SuccessImpl<T> extends Success<T> {
const _SuccessImpl({@required this.data, @required this.message})
: super(data: data, message: message);
@override
final T data;
@override
final String message;
@override
_SuccessImpl<T> copyWith(
{Object data = superEnum, Object message = superEnum}) =>
_SuccessImpl(
data: data == superEnum ? this.data : data as T,
message: message == superEnum ? this.message : message as String,
);
@override
String toString() => 'Success(data: ${this.data}, message: ${this.message})';
@override
List<Object> get props => [data, message];
}
/// Error case Of the Result
@immutable
abstract class Error<T> extends Result<T> {
const Error() : super(_Result.Error);
/// Error case Of the Result
factory Error.create() = _ErrorImpl<T>;
}
@immutable
class _ErrorImpl<T> extends Error<T> {
const _ErrorImpl() : super();
@override
String toString() => 'Error()';
}
Below is just one of the use-case of Super Enums
. It can be used wherever you want to manage state.
// Creating an StreamController of type Result<int>
final _resultController = StreamController<Result<int>>();
// Adding a success state to the stream controller
_resultController.add(Result.success(
data: 333,
message: 'Success',
));
// Adding an error state to the stream controller
_resultController.add(Result.error());
// Listening to all the possible Result states
_resultController.stream.listen((result) {
result.when(
onSuccess: (data) => print(data.message), // Success
onError: () => print('Error Occurred'), // Error Occurred
);
});
A sample UseClass()
example.
class MySuccess {
MySuccess(this.fieldA);
final String fieldA;
}
class MyError {
MyError(this.fieldA, this.fieldB);
final String fieldA;
final int fieldB;
}
@superEnum
enum _ResultUnion {
@UseClass(MySuccess)
Success,
@UseClass(MyError)
Error,
}
This project is a starting point for a Dart package, a library module containing code that can be shared easily across multiple Flutter or Dart projects.
For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.