Composition with Dart Mixins
Inheritance hierarchies depth-first into AnimatedWidget extends Widget extends Diagnosticable get wide fast. Mixins compose behaviors sideways: class _State extends State<Foo> with TickerProviderStateMixin, AutomaticKeepAliveClientMixin. No single base class owns all combinations. Dart mixins are linearized into one superclass chain at compile time—understand on constraints and order or super calls land on the wrong implementation.
Basic mixin
mixin Timestamped {
late final DateTime createdAt = DateTime.now();
String ageLabel() {
final diff = DateTime.now().difference(createdAt);
return '${diff.inMinutes}m ago';
}
}
class Post with Timestamped {
Post(this.title);
final String title;
}
class Comment with Timestamped {
Comment(this.body);
final String body;
}
Both get timestamp behavior without shared Post/Comment base class.
Multiple mixins and linearization
class Service with Loggable, Retryable implements Api {}
// Linearization: Service → Retryable → Loggable → Object
Order matters when mixins override same method—later mixins win in conflict resolution per Dart spec.
mixin on — super constraint
abstract mixin class Validator on FormFieldState<dynamic> {
bool validateField() {
final valid = widget.validator?.call(value) == null;
setState(() => hasError = !valid);
return valid;
}
}
class _EmailFieldState extends FormFieldState<String> with Validator {
// can call super.setState, access widget, value
}
Without on FormFieldState, mixin cannot assume setState exists.
mixin class (Dart 3)
mixin class DismissibleBehavior {
void onDismiss() {}
}
class Card extends StatelessWidget with DismissibleBehavior {}
Single declaration serves as mixin and class—reduces duplication.
Flutter State mixins
class _ChartState extends State<Chart>
with TickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
TickerProviderStateMixin implements TickerProvider for AnimationController vsync.
AutomaticKeepAliveClientMixin — preserve TabBarView state:
class _FeedTabState extends State<FeedTab> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context); // required
return ListView(/* ... */);
}
}
Mixins vs extension methods
Extensions add methods without runtime polymorphism—cannot override or access private state.
Mixins become part of class—override works, private members in same library accessible.
Use extensions for API sugar on types you do not own; mixins for shared stateful behavior.
Anti-patterns
God mixin — 400 lines of unrelated helpers. Split into focused mixins.
Hidden API — consumers of class X with A, B, C discover methods not in class declaration. Document mixed-in APIs or use explicit interfaces.
Mixin calling super without on — compile error or wrong super target.
Testing
Mixins test via minimal host class:
class _Host with MyMixin {}
test('MyMixin formats', () {
expect(_Host().format(), 'expected');
});
Mixin linearization in depth
Dart resolves multiple inheritance via linearization — a deterministic order for super calls:
class A { void greet() => print('A'); }
mixin B on A { @override void greet() { super.greet(); print('B'); } }
mixin C on A { @override void greet() { super.greet(); print('C'); } }
class D extends A with B, C {}
void main() => D().greet(); // prints: A, C, B
Order is D → C → B → A. Later mixins in the with clause are closer in the chain — with B, C means C's methods take precedence over B's in conflicts. When mixins override the same method, only the frontmost mixin's version runs unless it calls super.
This matters when composing logging, validation, and caching mixins that all wrap the same method — order determines wrapping sequence.
Practical Flutter mixin patterns
ScrollBehavior mixin for custom scrolling:
mixin CustomScrollBehavior on StatelessWidget {
ScrollBehavior get scrollBehavior => const MaterialScrollBehavior()
.copyWith(physics: const BouncingScrollPhysics());
}
RouteAware mixin for analytics:
mixin AnalyticsRouteAware<T extends StatefulWidget> on State<T>, RouteAware {
@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context)!);
}
@override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
@override
void didPush() => analytics.logScreenView(widget.runtimeType.toString());
}
Debounced input mixin:
mixin DebouncedSearch on State {
Timer? _debounce;
void onSearchChanged(String query, void Function(String) search) {
_debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 300), () => search(query));
}
@override
void dispose() {
_debounce?.cancel();
super.dispose();
}
}
Mixins vs abstract classes vs interfaces
| Mechanism | State | Multiple | super calls | Use when |
|---|---|---|---|---|
| Abstract class | Yes | No (extends one) | Yes | IS-A relationship |
| Mixin | Yes | Yes (multiple) | Yes (with on) |
Orthogonal behavior |
Interface (implements) |
No | Yes | No | Contract only |
| Extension | No | N/A | No | Syntax sugar on existing types |
Prefer implements for explicit contracts that consumers should know about. Use mixins for implementation details the consumer doesn't need to know about (like TickerProviderStateMixin).
Failure modes
- God mixin — 400+ lines mixing unrelated behaviors; split into focused mixins
- Wrong linearization order —
with Loggable, Retryablevswith Retryable, Loggableproduces different super chains - Mixin without
oncalling super — compile error or silent wrong target - Forgetting
super.build(context)— AutomaticKeepAliveClientMixin requires it - Hidden API surface — consumers don't know mixed-in methods exist; document or use interfaces
Production checklist
- Mixins are small and focused (<100 lines)
onclause used when mixin calls super on a known base- Linearization order documented when multiple mixins override same method
- Flutter State mixins call required super methods (build, dispose)
- Tests use minimal host class to verify mixin behavior
- Public API documented — mixed-in methods listed in class docs or interfaces
Prefer composition via mixins over deep inheritance hierarchies — but cap at 2–3 mixins per class to keep linearization understandable. If you need more, the class probably wants a dedicated helper or service object instead.
Common production mistakes
Teams get mixins composition wrong in predictable ways:
- Skipping failure-mode rehearsal — run a game day or fault injection exercise before peak traffic, not after the first outage.
- Missing correlation context — every error path should carry request, trace, or tenant identifiers so incidents are debuggable.
- Optimizing for demo, not steady state — load tests, cache warm-up, and cold-start paths matter more than local dev latency.
- Undocumented trade-offs — if you chose speed over strict correctness (or vice versa), write that down for the next engineer.
Production implementations of mixins composition fail when staging mirrors production topology poorly, rollback is untested, and on-call runbooks describe the happy path only.
Resources
- Dart mixins documentation
- Dart mixin class modifier
- Flutter TickerProviderStateMixin
- Effective Dart: Design — mixins
Frequently asked questions
What is a Dart mixin?
A mixin adds methods and fields to a class without requiring IS-A inheritance from a mixin type. Apply with 'class X extends Y with MixinA, MixinB'. Mixins flatten into the class linearization order—no duplicate diamond inheritance from mixin methods if composed correctly.
What does 'mixin on Class' mean?
The on clause restricts which classes can use the mixin—you can only mix in on subclasses of the stated base. This lets the mixin call super methods on a known API, e.g. mixin Validated on FormState { void validate() { super.validate(); ... } }.
When should I prefer mixins over inheritance?
Use mixins for orthogonal reusable behaviors—logging, serialization, comparison, ticker management—applied to unrelated classes. Use inheritance for true subtype relationships. Over-mixinning creates implicit API surfaces—prefer small focused mixins over god mixins.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →