Home
Dart
OOP
Flutter
Contact

OOP: Mixins & Extension Methods

Reuse code across unrelated classes with mixins and extend existing APIs with extensions.

What Are Mixins?

Mixins are a way to reuse code across multiple, unrelated class hierarchies. Unlike inheritance, which creates an "is-a" relationship, mixins create a "can-do" relationship. They let you add specific capabilities to any class without polluting the inheritance chain.

In Dart, you define a mixin with the mixin keyword and apply it to a class using the with keyword. A class can use multiple mixins, and mixins can even be constrained to only work with certain types. This makes them incredibly flexible for cross-cutting concerns like logging, serialization, or analytics — capabilities that many unrelated classes might need.

For example, both a User class and a Product class might need logging, but they have nothing else in common. A mixin solves this cleanly without forcing them into the same inheritance hierarchy.

Mixins are powerful but can lead to confusion if overused. Follow these guidelines:

📐 When to Use Mixins vs Inheritance
  • Inheritance (extends): Use when there's a clear "is-a" relationship. A Dog is an Animal.
  • Mixins (with): Use when adding capabilities that could apply to unrelated classes. A Dog can be Serializable. A Car can be Serializable too.
  • Interfaces (implements): Use when you need a contract without any shared implementation.

Mixin Constraints

You can restrict a mixin to only work with certain types using on:

mixin_constraints.dart
// This mixin can ONLY be used on classes that extend Streamable
mixin StreamAnalytics on Streamable {
  void trackEvent(String event) {
    print('Tracking: $event on ${streamName}');
    if (isStreaming) log(event);
  }
}

What Are Mixins?

Mixins allow you to add capabilities to classes without using inheritance. They solve the "deadly diamond problem" by providing reusable behavior that can be composed into any class:

mixin_basics.dart
mixin Logger {
  String get logPrefix => '[LOG]';

  void log(String message) {
    print('$logPrefix $message');
  }
}

mixin Timestampable {
  String get timestamp => DateTime.now().toIso8601String();

  void logWithTime(String message) {
    print('[$timestamp] $message');
  }
}

The with Keyword

Use with to mix capabilities into a class. You can combine multiple mixins:

services.dart
// A service class that uses both mixins
class AnalyticsService with Logger, Timestampable {
  String get logPrefix => '[ANALYTICS]';

  void trackEvent(String event) {
    logWithTime('Event tracked: $event');
  }
}

class AuthService with Logger {
  String get logPrefix => '[AUTH]';

  void login(String user) {
    log('User logged in: $user');
  }
}
ℹ️ Mixin Constraint Use mixin Logger on SomeClass to require that only subclasses of SomeClass can use the mixin.

Extension Methods

Add methods to existing types without modifying their source code:

string_extensions.dart
extension StringValidation on String {
  bool get isEmail => contains('@') && contains('.');
  bool get isPhoneNumber => startsWith('+') && length >= 10;
  String get capitalized =>
      length > 0 ? '${this[0].toUpperCase()}${substring(1)}' : this;
}

extension ListSort<T extends Comparable<T>> on List<T> {
  List<T> get naturalSort => [...this]..sort();
  List<T> get reversedSort => [...this]..sort((a, b) => b.compareTo(a));
}

void main() {
  print('[email protected]'.isEmail);   // true
  print('hello'.capitalized);         // Hello

  var nums = [3, 1, 4, 1, 5];
  print(nums.naturalSort);             // [1, 1, 3, 4, 5]
  print(nums.reversedSort);            // [5, 4, 3, 1, 1]
}

Real-World Example: Logger Mixin for Analytics

analytics.dart
mixin AnalyticsTracker {
  void trackPageView(String page) {
    print('📊 Page view: $page at ${DateTime.now()}');
  }

  void trackAction(String action, [Map? params]) {
    print('📊 Action: $action ${params ?? ''}');
  }
}

class HomeScreenService with Logger, AnalyticsTracker {
  void loadContent() {
    log('Loading home content...');
    trackPageView('/home');
    trackAction('content_loaded', {'items': 12});
  }
}

class CheckoutService with Logger, AnalyticsTracker {
  void processOrder(String orderId) {
    log('Processing order: $orderId');
    trackPageView('/checkout');
    trackAction('order_submitted', {'order_id': orderId});
  }
}

Summary

  • mixin defines reusable behavior without inheritance
  • with mixes one or more mixins into a class
  • extension adds methods to existing types without modifying them
  • Mixins are perfect for cross-cutting concerns: logging, analytics, serialization
🚀 Next Step Continue to Flutter Architecture & Lifecycle to start building UIs.
K
Kesavaraja Murugesan
Flutter Developer & Educator

Flutter developer with 3 years of experience building production-grade mobile applications. Passionate about teaching clean architecture patterns and helping developers write maintainable, scalable code.

← Abstraction Next: Flutter Architecture →