copyable 0.0.1+2 copyable: ^0.0.1+2 copied to clipboard
A Flutter package for giving and standardizing copy functionality to Dart classes.
copyable #
A package for giving copy capabilities to classes, both local (i.e. in your source code), and foreign (i.e. defined in a third-party library).
Table of Contents
Examples #
The best way to learn is with examples.
Usage #
The copyable
package defines two interfaces for giving copy-like
functionality to Dart classes: Copyable
and Copier
.
Once you define a class that implements either of the two interfaces, you can copy instances of the class like this:
Copyable
class Point implements Copyable<Point> {
final int x;
final int y;
final Shape parent;
Point(this.x, this.y, {this.parent});
// Implement Copyable interface
}
Point origin = Point(0, 0);
// Copy
Point origin_copy = origin.copy();
// Copy, overriding some fields
Point x_intercept = origin.copyWith(x: 5);
Copier
class PointCopier implements Copier<Point> {
// Implement Copier interface
}
PointCopier pointCopier = PointCopier();
Point origin = Point(0, 0);
// Copy
Point origin_copy = pointCopier.copy(origin);
// Copy, overriding some fields
Point x_intercept = pointCopier.copyWith(x: 5);
Whether to use Copyable
or Copier
is a matter of preference: the
Copyable
pattern adds copy functionality directly into a class,
where as the Copier
pattern adds copy functionality indirectly by
creating an entirely new class.
Setup #
Add the following dependency to your pubspec.yaml
dependencies:
copyable: ^0.0.1
Using the code generation tools requires additional setup.
Copyable #
Interface
/// An interface for copying objects.
abstract class Copyable<T> {
/// Copy the current object.
@required T copy();
/// Copy the current object, overriding with non-null properties of
/// `master` when present.
@required T copyWith(T master);
/// Copy the current object, overriding with the given properties.
@required T copyWithProperties(/* Add named properties here; typically
these are object fields.*/);
}
Implementation
import 'package:copyable/copyable.dart';
class Point implements Copyable<Point> {
final int x;
final int y;
Point parent;
Point({
this.x,
this.y,
this.parent
});
// Copyable Implementation
@override
Point copy() => _copy(this);
@override
Point copyWith(Point master) => _copy(master);
@override
Point copyWithProperties({
int x,
int y,
Point parent
}) => _copy(this,
x: x,
y: y,
parent: parent
);
static Point _copy(Point master, {
int x,
int y,
Point parent
}) {
return Point(
x : x ?? master?.x,
y : y ?? master?.y,
parent : parent ?? master?.parent
);
}
}
Code Generation
If you don't want to manually implement the Copyable
interface (I
know, it's a lot of boilerplate :/ ), have no fear! Check out the
code generation section.
Copier #
Interface
abstract class Copier<T> {
/// Necessary in order to support chaining of `copy()` calls.
/// Basically a way to bootstrap a temporary master (from a previous `copy
/// ()` call), it holds the result of the previous `.copy()` call so that
/// the user doesn't have to re-wrap the result in a `Copier` instance.
@required T master;
/// The default template for copying. This will be the last
/// fallback for any undefined properties of the object being copied.
/// Typically just a bare-bones object (i.e. `T()`).
@required T get defaultMaster;
// Convenience Methods
/// Copies the given object `master` and returns a new instance of `Copier<T>`
/// whose master is the newly copied object.
@required Copier<T> copy(T master);
/// Copies the given object `master` and returns it.
@required T copyAndResolve(T master);
/// Copies `this.master` with the given parameters only. Returns a new
/// instance of `Copier<T>` whose master is the newly copied object.
/// **Note:** Concrete implementations of `Copier<T>` should add optional
/// named parameters corresponding to the instance properties of `T`.
@required Copier<T> copyWith(/* {{ Properties here! }} */);
/// Copies `this.master` with the given parameters only. Returns the newly
/// copied object.
/// **Note:** Concrete implementations of `Copier<T>` should add optional
/// named parameters corresponding to the instance properties of `T`.
@required T copyWithAndResolve(/* {{ Properties here! }} */);
/// Returns the result of the most recent copy (i.e. this.master).
/// Literally the only code in this function should be `return this.master;`
@required T resolve();
}
Implementation
class CircleCopier implements Copier<Circle> {
CircleCopier([this.master]);
Circle master;
Circle get defaultMaster {
return Circle(radius: 1);
}
dynamic _copy(Circle master,
{bool resolve = false, int radius, int centerX, int centerY}) {
master = master ?? this.master;
Circle newCircle = Circle(
radius: radius ?? master?.radius ?? defaultMaster.radius,
centerX: centerX ?? master?.centerX ?? defaultMaster.centerX,
centerY: centerY ?? master?.centerY ?? defaultMaster.centerY);
return resolve ? newCircle : CircleCopier(newCircle);
}
@override
CircleCopier copy(Circle master) {
return this._copy(
master,
resolve: false,
) as CircleCopier;
}
@override
Circle copyAndResolve(Circle master) {
return this._copy(
master,
resolve: true,
) as Circle;
}
@override
CircleCopier copyWith({int radius, int centerX, int centerY}) {
return this._copy(this.master,
resolve: false,
radius: radius,
centerX: centerX,
centerY: centerY) as CircleCopier;
}
@override
Circle copyWithAndResolve({int radius, int centerX, int centerY}) {
return this._copy(this.master,
resolve: true,
radius: radius,
centerX: centerX,
centerY: centerY) as Circle;
}
Circle resolve() {
return this.master;
}
}
Code Generation
If you don't want to manually implement the Copier
interface (I know,
it's a lot of boilerplate :/ ), have no fear! Check out the
code generation section.
Code Generation #
Most of the code needed to implement either Copyable
or Copier
is
boilerplate and not fun. Instead, you can generate the respective
Copyable
or Copier
code using build_runner
.
Setup #
Add the following dev dependencies to your pubspec.yaml
:
dev_dependencies:
build_runner: ^1.0.0
build_verify: ^1.1.0
Make sure to run pub get
or flutter packages get
.
If you don't already a build.yaml
file, create one in your project
root directory (wherever pubspec.yaml
is). Then, add the following to
your build.yaml
:
targets:
$default:
builders:
# TODO: Add builders
Builders
There are four different builders you can use to generate copy-code. Each builder corresponds to one of the four use cases described earlier in [link here]:
- | Copyable | Copier |
---|---|---|
Local | copyable |
copier |
Foreign | foreignCopyableLib |
foreignCopierLib |
Once you know which builder(s) you want to use, add them your
build.yaml
with the pattern copyable|{BUILDER_NAME}
:
targets:
$default:
builders:
copyable|copyable:
generate_for:
- lib/point.dart
copyable|foreignCopyableLib:
generate_for:
- lib/circle.dart
# More builders if needed...
Usage #
- Import
package:copyable/generator.dart
.
copyable
- Annotate the class you want to generate copy code for with
@generate_copyable
. - Add the following to the top of your file:
part '$FILE_NAME.g.dart';
- Run the builder (see below). This will generate a mixin with the
necessary copy-code as a
part of
file - Add the generated mixin to your original class.
copier
-
Annotate the class you want to generate copy code for with
@GenerateCopier(defaultObjectCode: $DEFAULT)
.In place of
$DEFAULT
, pass a string of the code to use to instantiate a "default" instance of the class; for example,@GenerateCopier(defaultObjectCode: 'Circle(radius: 1')
-
Add the following to the top of your file:
part '$FILE_NAME.g.dart';
- Run the builder (see below). This will generate a separate class
with the necessary copy-code as a
part of
file.
foreignCopyableLib
- Create a
CopyableMeta
instance representing the class you want to generate a copyable version of for. - Create a
CopyMetaGenerator
instance and pass in the meta object. - Run the builder (see below). This will generate a separate library generated classes.
foreignCopyableLib
- Create a
CopierMeta
instance representing the class you want to generate a copyable version of for. - Create a
CopyMetaGenerator
instance and pass in the meta object. - Run the builder (see below). This will generate a separate library generated classes.
Running #
In the directory where your build.yaml
is, run the following:
Flutter project
flutter packages pub run build_runner build
Non-flutter project
pub run build_runner build