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).
The best way to learn is with examples.
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.
Add the following dependency to your pubspec.yaml
dependencies:
copyable: ^0.0.1
Using the code generation tools requires additional setup.
/// 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.*/);
}
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
);
}
}
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.
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();
}
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;
}
}
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.
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
.
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
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...
- Import
package:copyable/generator.dart
.
- 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.
-
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.
- 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.
- 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.
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