Composition with Dart Mixins

FlutterDart
Share on LinkedIn

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

Production checklist

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:

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

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 →