mix_generator
Code generator for the Mix styling framework. Processes mix_annotations to generate boilerplate code for Spec, Styler, Mix, and widget-wrapper classes.
Installation
flutter pub add mix
flutter pub add mix_annotations
flutter pub add --dev mix_generator
flutter pub add --dev build_runner
Or in pubspec.yaml:
dependencies:
mix: ^2.0.0
mix_annotations: ^2.0.0
dev_dependencies:
build_runner: ^2.4.0
mix_generator: ^2.0.0
What It Generates
The generator emits several surfaces with deliberately different shapes, chosen to match the type they're paired with:
@MixableSpecemits a rich mixin (mixin _$<Name> implements Spec<T>, Diagnosticable) that fully completes the Spec contract on its own. Specs are immutable value types with no shared concrete behavior to inherit, so the generated mixin can be self-contained — user code only writeswith _$<Name>.@MixableSpec(target: Widget.new)also emits a full generated Styler class into the same.g.dartpart file as the spec mixin. The generated class owns fields, constructors, factories, fluent methods,call(), merge, resolve, diagnostics, and props.@MixableStyleremits a legacy slim mixin (mixin _$<Name>Mixin on Style<S>, Diagnosticable) that fills in per-field plumbing for handwritten styler classes.@Mixableemits a slim mixin (mixin _$<Name>Mixin on Mix<T>[, DefaultValue<T>][, Diagnosticable]) for the same reason — Mix subclasses commonly compose intermediate base classes (e.g.,class BoxConstraintsMix extends ConstraintsMix<BoxConstraints>) and the user keeps that inheritance chain.@MixableModifieremits a self-contained modifier mixin (mixin _$<Name> implements WidgetModifier<T>, Diagnosticable) plus its corresponding<Name>Mixclass.
If the asymmetry feels surprising, the rule is: rich/full shape for pure-data specs and their generated stylers; slim shape for types whose extends chain carries shared state or behavior.
The @MixWidget annotation generates a full StatelessWidget class (not a mixin) that wraps a top-level Style<S> factory — see the @MixWidget section below.
From @MixableSpec — Spec mixin
Generates a self-contained _$<Name> mixin (implements Spec<Name>, Diagnosticable) for immutable Spec classes. User code declares the spec with a single with _$<Name> — Equatable-style equality and Diagnosticable's concrete surface are inlined by the generator:
Type get type— the concrete specTypecopyWith()— create modified copieslerp()— smooth interpolation between specspropsby default,==,hashCode— deep equality viapropsEquals/propsHashhelperstoString({DiagnosticLevel minLevel}),toStringShort(),toDiagnosticsNode()— Flutter inspector integrationdebugFillProperties()— diagnostic output
No with Equatable and no with Diagnosticable on the user class — the mixin carries both.
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:mix/mix.dart';
import 'package:mix_annotations/mix_annotations.dart';
part 'box_spec.g.dart';
@MixableSpec()
@immutable
final class BoxSpec with _$BoxSpec {
@override
final Color? color;
@override
final double? width;
@override
final double? height;
@override
final AlignmentGeometry? alignment;
const BoxSpec({this.color, this.width, this.height, this.alignment});
}
When @MixableSpec generates a Styler, the host library must import the Mix
runtime and any Flutter or local types referenced by the generated styler code,
because the generated output is a Dart part and cannot declare its own imports.
Legacy declaration shape. Pre-2.0 generators emitted a slim _$<Name>SpecMethods mixin and required class <Name> extends Spec<<Name>> with Diagnosticable, _$<Name>SpecMethods. The generator now emits a @Deprecated typedef _$<Name>SpecMethods = _$<Name>; alongside every spec mixin so those legacy declarations keep compiling — you'll just see a deprecation warning. Migrate to class <Name> with _$<Name> to silence it; the alias is scheduled for removal in mix_generator 3.0. One observable change for legacy callers: toString() now routes through Diagnosticable.toDiagnosticsNode, replacing Equatable's Name(field: …) output with Flutter's diagnostic-node format.
From @MixableStyler — legacy Styler mixin
Generates a _$<Name>Mixin for handwritten Styler classes with:
- Setter methods for each
$-prefixed field merge()— combine stylesresolve()— resolve to the corresponding SpecdebugFillProperties()— diagnostics supportprops— equality comparison
part 'box_style.g.dart';
@MixableStyler()
class BoxStyler extends Style<BoxSpec>
with Diagnosticable, _$BoxStylerMixin {
final Prop<Color>? $color;
final Prop<double>? $width;
final Prop<double>? $height;
final Prop<AlignmentGeometry>? $alignment;
// ...
}
From @Mixable — Mix mixin
Generates a _$<Name>Mixin for compound property types with:
merge()— combine mix instancesresolve()— resolve to the target typeprops— equality comparisondebugFillProperties()— diagnostics support
part 'box_constraints_mix.g.dart';
@Mixable()
final class BoxConstraintsMix extends ConstraintsMix<BoxConstraints>
with DefaultValue<BoxConstraints>, Diagnosticable, _$BoxConstraintsMixMixin {
final Prop<double>? $minWidth;
final Prop<double>? $maxWidth;
final Prop<double>? $minHeight;
final Prop<double>? $maxHeight;
// ...
}
From @MixableModifier — modifier mixin and ModifierMix
Generates a self-contained _$<Name> mixin for an immutable
WidgetModifier, plus a <Name>Mix class that can be used in styles. The mixin
provides type, copyWith, optional lerp, value equality, diagnostics, and
the build contract. The Mix class provides constructors, resolve, merge,
diagnostics, and props.
part 'opacity_modifier.g.dart';
@MixableModifier()
final class OpacityModifier with _$OpacityModifier {
@override
final double opacity;
const OpacityModifier([double? opacity]) : opacity = opacity ?? 1.0;
@override
Widget build(Widget child) => Opacity(opacity: opacity, child: child);
}
Pass lerp: false when the modifier implements custom interpolation. A field
may use @MixableField(setterType: SomeMix) when its style-facing value should
be a Mix type; the setter type must resolve to the field's runtime value type.
From @MixWidget — widget wrapper
Generates a StatelessWidget wrapper around a top-level Style<S> variable or function. The wrapper's constructor mirrors the factory's parameters (for function-backed styles) plus the styler's call() parameters; build() invokes the factory and forwards the parameters through to the resulting widget.
part 'card.g.dart';
@mixWidget
final cardStyle = BoxStyler()
.color(Colors.white)
.borderRounded(8)
.paddingAll(16);
// Generates:
// class Card extends StatelessWidget {
// const Card({super.key, this.child});
// final Widget? child;
// @override Widget build(BuildContext context) =>
// cardStyle.call(key: this.key, child: this.child);
// }
The widget name is derived from a lower-camel-case *Style identifier (cardStyle → Card); pass @MixWidget(name: 'X') to override. Function-backed factories thread their args before the styler call(): @MixWidget Style<S> badge({Color? color}) => ... generates a Badge({this.color, this.child}) constructor.
When a function-backed factory declares a named, non-nullable enum parameter
named variant, the generator also emits one named constructor per accessible
enum value:
@MixWidget()
ButtonStyler buttonStyle({ButtonVariant variant = .solid}) => ...;
// Generates both the backwards-compatible `Button(variant: ...)` and:
// const Button.solid({super.key, ...}) : variant = ButtonVariant.solid;
// const Button.ghost({super.key, ...}) : variant = ButtonVariant.ghost;
Each named constructor omits the variant argument and preserves every other
factory and styler call() parameter. Enum-value documentation and deprecation
metadata are carried onto the matching constructor. Nullable, positional, and
non-enum variant parameters retain the single-constructor behavior. Variant
constructors are also skipped when an enum value conflicts with a generated
widget type parameter.
See mix_annotations for the full annotation contract (parameter rules, Key? key forwarding, naming/visibility constraints).
Field-level control with @MixableField
// Skip setter generation.
@MixableField(ignoreSetter: true)
final Prop<Matrix4>? $transform;
// Override the setter parameter type.
@MixableField(setterType: List<Shadow>)
final Prop<List<Shadow>>? $shadows;
Running the Generator
dart run build_runner build --delete-conflicting-outputs
Or within the Mix monorepo:
melos run gen:build
Generated files use the .g.dart extension and should be committed to version control.
Debugging
VS Code
- Set breakpoints in the generator source files under
lib/src/. - Select the "Debug build_runner" launch configuration.
- Press F5.
Manual
dart --enable-vm-service=8888 --pause-isolates-on-start run build_runner build --verbose
Then connect your debugger to localhost:8888.
Learn More
- Mix documentation
- mix — Core framework
- mix_annotations — Annotation definitions
- GitHub repository
Libraries
- mix_generator
- Code generators for Mix specs, stylers, mix values, and widget wrappers.