Question #25MediumFlutter Basics

How to use and usage of mixin in flutter

#flutter

Answer

Overview

Mixins in Dart allow you to reuse code across multiple class hierarchies without inheritance. A mixin is a class whose methods can be used by other classes without being a parent class.


What is a Mixin?

A mixin is a way to add functionality to classes without using traditional inheritance.

dart
// Traditional inheritance - limited to one parent
class Animal {}
class Dog extends Animal {} // ✅
class Cat extends Animal {} // ✅
class RobotDog extends Animal, Robot {} // ❌ Multiple inheritance not allowed

// With mixins - can add multiple behaviors
class Dog extends Animal with CanBark, CanRun {} // ✅
class RobotDog extends Robot with CanBark, CanRun {} // ✅

Syntax

Define a Mixin

dart
mixin CanFly {
  void fly() {
    print('Flying!');
  }
  
  double get altitude => 100.0;
}

mixin CanSwim {
  void swim() {
    print('Swimming!');
  }
}

Use a Mixin

dart
class Duck extends Animal with CanFly, CanSwim {
  void quack() {
    print('Quack!');
  }
}

void main() {
  final duck = Duck();
  duck.fly();    // From CanFly mixin
  duck.swim();   // From CanSwim mixin
  duck.quack();  // From Duck class
}

Mixin vs Class

dart
// Regular class - can be extended AND used as mixin
class Flyable {
  void fly() => print('Flying');
}

// Pure mixin - cannot be extended, only mixed in
mixin Swimmable {
  void swim() => print('Swimming');
}

// Usage
class Bird extends Animal with Flyable {} // ✅
class Fish extends Animal with Swimmable {} // ✅

class Eagle extends Flyable {} // ✅ Can extend class
class Shark extends Swimmable {} // ❌ Cannot extend mixin

Mixin with 'on' Keyword

Restrict which classes can use the mixin.

dart
class Animal {
  String name;
  Animal(this.name);
}

// This mixin can only be used with Animal or its subclasses
mixin CanBark on Animal {
  void bark() {
    print('$name says: Woof!'); // Can access Animal's properties
  }
}

class Dog extends Animal with CanBark {
  Dog(String name) : super(name);
}

class Robot with CanBark {} // ❌ Error: Robot is not Animal

Flutter Use Cases

1. Ticker Mixin (Animations)

dart
import 'package:flutter/material.dart';

class AnimatedScreen extends StatefulWidget {
  
  _AnimatedScreenState createState() => _AnimatedScreenState();
}

class _AnimatedScreenState extends State<AnimatedScreen>
    with SingleTickerProviderStateMixin { // Mixin for animation
  late AnimationController _controller;
  
  
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this, // 'this' provides Ticker from mixin
      duration: Duration(seconds: 2),
    )..repeat();
  }
  
  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  
  Widget build(BuildContext context) {
    return RotationTransition(
      turns: _controller,
      child: FlutterLogo(size: 100),
    );
  }
}

2. Multiple Animations

dart
class _MyWidgetState extends State<MyWidget>
    with TickerProviderStateMixin { // Multiple tickers
  late AnimationController _controller1;
  late AnimationController _controller2;
  
  
  void initState() {
    super.initState();
    _controller1 = AnimationController(vsync: this, duration: Duration(seconds: 1));
    _controller2 = AnimationController(vsync: this, duration: Duration(seconds: 2));
  }
}

3. AutomaticKeepAlive (TabBar/PageView)

dart
class TabContent extends StatefulWidget {
  
  _TabContentState createState() => _TabContentState();
}

class _TabContentState extends State<TabContent>
    with AutomaticKeepAliveClientMixin { // Keeps state alive
  
  
  bool get wantKeepAlive => true; // Prevent disposal
  
  
  Widget build(BuildContext context) {
    super.build(context); // Must call when using AutomaticKeepAliveClientMixin
    return ListView.builder(
      itemCount: 100,
      itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
    );
  }
}

4. WidgetsBinding Observer

dart
class _AppState extends State<App> with WidgetsBindingObserver {
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  
  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  
  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      print('App paused');
    } else if (state == AppLifecycleState.resumed) {
      print('App resumed');
    }
  }
  
  
  Widget build(BuildContext context) => Container();
}

Custom Mixins

Example: Logger Mixin

dart
mixin LoggerMixin {
  void log(String message) {
    print('[${runtimeType}] $message');
  }
  
  void logError(String error) {
    print('[${runtimeType}] ERROR: $error');
  }
}

class UserService with LoggerMixin {
  void fetchUser() {
    log('Fetching user...');
    // API call
    log('User fetched successfully');
  }
}

class ProductService with LoggerMixin {
  void fetchProducts() {
    log('Fetching products...');
    logError('Network error');
  }
}

Example: Validation Mixin

dart
mixin ValidationMixin {
  bool isValidEmail(String email) {
    return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
  }
  
  bool isValidPhone(String phone) {
    return RegExp(r'^\d{10}$').hasMatch(phone);
  }
  
  bool isNotEmpty(String? value) {
    return value != null && value.isNotEmpty;
  }
}

class RegistrationForm extends StatefulWidget {
  
  _RegistrationFormState createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm>
    with ValidationMixin {
  
  String? validateEmail(String? value) {
    if (!isNotEmpty(value)) return 'Email required';
    if (!isValidEmail(value!)) return 'Invalid email';
    return null;
  }
  
  
  Widget build(BuildContext context) {
    return TextFormField(
      validator: validateEmail,
      decoration: InputDecoration(labelText: 'Email'),
    );
  }
}

Multiple Mixins

dart
mixin CanWalk {
  void walk() => print('Walking');
}

mixin CanRun {
  void run() => print('Running');
}

mixin CanJump {
  void jump() => print('Jumping');
}

class Human with CanWalk, CanRun, CanJump {
  void moveAround() {
    walk();
    run();
    jump();
  }
}

Mixin Order Matters

dart
mixin A {
  void greet() => print('A');
}

mixin B {
  void greet() => print('B');
}

class MyClass with A, B {} // B's greet() wins (last one)

void main() {
  MyClass().greet(); // Prints: B
}

Best Practices

Important: Use mixins for behavior, not state

✅ Do

dart
// Good - Behavior mixin
mixin Logger {
  void log(String msg) => print(msg);
}

// Good - Single responsibility
mixin NetworkMixin {
  Future<void> fetchData() async {}
}

❌ Don't

dart
// Bad - State in mixin (use composition instead)
mixin UserData {
  String userName = ''; // Avoid state
  int userAge = 0;
}

Prefer Composition Over Mixins for State

dart
// ✅ Better - Use composition for state
class UserData {
  String userName;
  int userAge;
  UserData(this.userName, this.userAge);
}

class UserScreen {
  final UserData data;
  UserScreen(this.data);
}

Common Flutter Mixins

MixinPurpose
text
SingleTickerProviderStateMixin
Single animation
text
TickerProviderStateMixin
Multiple animations
text
AutomaticKeepAliveClientMixin
Keep state in TabBar/PageView
text
WidgetsBindingObserver
App lifecycle events
text
RouteAware
Route navigation awareness

Resources