Dart Mixins — Reusable Behavior Composition
In this tutorial, you will learn about Dart Mixins. We cover key concepts, practical examples, and best practices to help you master this topic.
Dart mixins are a way to reuse a class's code in multiple class hierarchies by injecting the mixin's implementation into the class, without requiring the class to extend the mixin.
What You Will Learn
- Defining mixins with the
mixinkeyword - Applying mixins with the
withkeyword - Mixin restrictions with
onclause - Combining multiple mixins
- Comparing mixins to inheritance and interfaces
- Using mixins in Flutter widgets
Why It Matters
Mixins solve the problem of sharing behavior across unrelated classes without forcing them into a single inheritance hierarchy. This is especially useful in Flutter, where many built-in mixins provide capabilities like AutomaticKeepAliveClientMixin, TickerProviderStateMixin, and SingleTickerProviderStateMixin. Without mixins, you would either duplicate code across classes or force artificial inheritance relationships.
Real-World Use
The DodaTech Flutter app uses a LoggingMixin that adds timestamped logging to any class. The ValidationMixin provides form validation methods that are mixed into multiple form widgets. The AnalyticsMixin adds event tracking to screens without requiring them to extend a common base class.
Learning Path
flowchart LR A[Dart Interfaces] --> B[Dart Mixins\nYou are here] B --> C[Dart Generics] style B fill:#f90,color:#fff
Defining and Using Mixins
A mixin is defined with the mixin keyword and applied with with:
mixin LoggerMixin {
void log(String message) {
print('[LOG]: $message');
}
void error(String message) {
print('[ERROR]: $message');
}
}
class UserService with LoggerMixin {
void createUser(String name) {
log('Creating user: $name');
// Business logic...
log('User created: $name');
}
}
class OrderService with LoggerMixin {
void placeOrder(String item) {
log('Placing order for: $item');
// Business logic...
log('Order placed: $item');
}
}
void main() {
var userService = UserService();
userService.createUser('Alice');
var orderService = OrderService();
orderService.placeOrder('Widget');
}
Output:
[LOG]: Creating user: Alice
[LOG]: User created: Alice
[LOG]: Placing order for: Widget
[LOG]: Order placed: Widget
Both UserService and OrderService get the log and error methods without extending a common base class. The mixin is injected into each class independently.
Mixin with State
Mixins can have fields and state, unlike interfaces:
mixin CounterMixin {
int _count = 0;
int get count => _count;
void increment() {
_count++;
}
void reset() {
_count = 0;
}
}
class CounterWidget with CounterMixin {
void display() {
print('Current count: $count');
}
}
void main() {
var widget = CounterWidget();
widget.display(); // 0
widget.increment();
widget.increment();
widget.increment();
widget.display(); // 3
widget.reset();
widget.display(); // 0
}
Output:
Current count: 0
Current count: 3
Current count: 0
The mixin maintains its own _count field. Each class that uses the mixin gets its own copy of the field. This is more stateful than interfaces but more flexible than inheritance.
Multiple Mixins
A class can use multiple mixins, combining their behaviors:
mixin TimestampMixin {
DateTime get createdAt => DateTime.now();
}
mixin IdMixin {
String get id => DateTime.now().millisecondsSinceEpoch.toString();
}
mixin JsonSerializableMixin {
Map<String, dynamic> toJson();
}
class User with TimestampMixin, IdMixin, JsonSerializableMixin {
String name;
int age;
User(this.name, this.age);
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'age': age,
'createdAt': createdAt.toIso8601String(),
};
}
void main() {
var user = User('Alice', 30);
print(user.toJson());
}
Output:
{id: 1719523200000, name: Alice, age: 30, createdAt: 2026-06-28T...}
Multiple mixins are separated by commas after with. The class inherits all methods and fields from all mixins.
Mixin Restrictions (on Clause)
The on clause restricts which classes can use the mixin:
class Animal {
String name = '';
void breathe() => print('Breathing');
}
mixin Walker on Animal {
void walk() {
print('$name is walking');
}
}
mixin Swimmer on Animal {
void swim() {
print('$name is swimming');
}
}
// Dog can use Walker and Swimmer because it extends Animal
class Dog extends Animal with Walker, Swimmer {
Dog(String name) {
this.name = name;
}
}
// Bird can use Walker but not Swimmer
class Bird extends Animal with Walker {
Bird(String name) {
this.name = name;
}
}
// This would fail: String does not extend Animal
// class Fish extends String with Swimmer {}
void main() {
var dog = Dog('Buddy');
dog.walk();
dog.swim();
dog.breathe();
var bird = Bird('Tweety');
bird.walk();
}
Output:
Buddy is walking
Buddy is swimming
Breathing
Tweety is walking
The on Animal clause means only classes that extend Animal can use the Walker or Swimmer mixins. This ensures the mixin can safely access Animal's members.
Mixin Ordering and Super Calls
When multiple mixins are applied, the order matters. If a mixin calls super, the call goes to the next mixin in the chain:
mixin A {
String getMessage() => 'A';
}
mixin B on A {
@override
String getMessage() => 'B -> ${super.getMessage()}';
}
mixin C on B {
@override
String getMessage() => 'C -> ${super.getMessage()}';
}
class MyClass with A, B, C {
void printMessage() => print(getMessage());
}
void main() {
var obj = MyClass();
obj.printMessage();
}
Output:
C -> B -> A
The method calls are resolved from right to left in the with clause, and super calls go from left to right. This is called the linearization of mixins.
Mixins vs Inheritance vs Interfaces
Understanding when to use each:
// Inheritance: is-a relationship, shares implementation
class Vehicle {
void move() => print('Moving');
}
class Car extends Vehicle {}
// Interface: can-do relationship, no shared implementation
abstract class Flyable {
void fly();
}
class Airplane implements Flyable {
@override
void fly() => print('Flying');
}
// Mixin: has-a relationship, shares implementation without inheritance
mixin Electric {
void charge() => print('Charging battery');
}
class Tesla extends Car with Electric {}
void main() {
var tesla = Tesla();
tesla.move(); // From Vehicle
tesla.charge(); // From Electric
}
Output:
Moving
Charging battery
Use extends for is-a relationships. Use implements for can-do contracts. Use with for has-a reusable behavior.
Common Flutter Mixins
Flutter provides several useful mixins out of the box:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
// SingleTickerProviderStateMixin provides this
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this, // Provided by the mixin
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
SingleTickerProviderStateMixin provides the TickerProvider interface required by AnimationController. Without the mixin, you would need to manually manage a Ticker object.
Custom Mixin Example: Validation
A practical mixin for form validation:
mixin ValidationMixin {
String? validateEmail(String? email) {
if (email == null || email.isEmpty) return 'Email is required';
if (!email.contains('@') || !email.contains('.')) {
return 'Enter a valid email address';
}
return null;
}
String? validatePassword(String? password) {
if (password == null || password.isEmpty) return 'Password is required';
if (password.length < 8) return 'Password must be at least 8 characters';
if (!password.contains(RegExp(r'[A-Z]'))) {
return 'Password must contain an uppercase letter';
}
if (!password.contains(RegExp(r'[0-9]'))) {
return 'Password must contain a digit';
}
return null;
}
String? validateNotEmpty(String? value, String fieldName) {
if (value == null || value.trim().isEmpty) {
return '$fieldName cannot be empty';
}
return null;
}
}
class RegistrationForm with ValidationMixin {
void submit(String email, String password) {
var emailError = validateEmail(email);
var passwordError = validatePassword(password);
if (emailError != null || passwordError != null) {
print('Validation failed:');
if (emailError != null) print(' - $emailError');
if (passwordError != null) print(' - $passwordError');
} else {
print('Registration successful for $email');
}
}
}
void main() {
var form = RegistrationForm();
form.submit('invalid', 'weak');
print('---');
form.submit('alice@test.com', 'StrongPass1');
}
Output:
Validation failed:
- Enter a valid email address
- Password must be at least 8 characters
---
Registration successful for alice@test.com
Common Mistakes
Using mixins when a simple function suffices: If the mixin has no state and no
supercalls, a top-level function or static method may be simpler.Assuming mixins create an
is-arelationship: Mixins provide behavior, not identity.obj is LoggerMixinreturns true, but the intent is code reuse, not type hierarchy.Order sensitivity with overlapping methods: When two mixins define the same method, the rightmost mixin in the
withclause wins. Changing the order changes behavior.Not using
onclause when the mixin depends on superclass members: If a mixin accessesthis.someMethod(), it should declareonthe class that provides that method to prevent runtime errors.Overusing mixins for everything: Mixins are powerful but can make code hard to follow when overused. Prefer composition and simple inheritance for most cases.
Practice Questions
- What is the difference between a mixin and an interface?
- How does the
onclause restrict which classes can use a mixin? - What happens when two mixins define the same method?
- Why does Flutter's
SingleTickerProviderStateMixinuse theonclause withState? - Challenge: Design an
AuditMixinthat logs every method call with a timestamp. UsenoSuchMethodor runtime introspection to automatically log all invocations. Apply the mixin to aBankAccountclass.
Mini Project
Build a logging and monitoring system with mixins:
TimestampMixinthat adds creation timestampSerializationMixinthat addstoJson()andfromJson()CacheMixinthat adds in-memory Caching- Combine all three into a
CachedApiClient - Apply individual mixins to different classes
- Write tests verifying each mixin's behavior independently
FAQ
What is Next
Now that you understand mixins, learn about type-safe generic programming. Proceed to Dart Generics for parameterized types and collection type safety. Then explore Async Programming in Dart for futures and async-await.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro