Brain
EngineeringFlutter

Dart Language Essentials

Mixins and the on keyword, and how iterables differ from lists.

Mixins

A mixin is a block of behaviour you can attach to classes that otherwise have nothing in common, without putting them in the same inheritance chain.

What a mixin is

In Dart, a mixin is a way to reuse code in multiple class hierarchies. It allows you to "mix in" methods and properties from one class to another without establishing an inheritance relationship.

Why mixins exist

Dart has single inheritance

The primary reason for using mixins is that Dart, like many object-oriented languages, only supports single inheritance. This means a class can only directly extend one parent class:

class A {}
class B {}
class C extends A {} // Valid
class D extends A, B {} // Invalid - can't extend multiple classes

Mixins solve this limitation by providing a mechanism to reuse code across different class hierarchies without multiple inheritance.

Composition over inheritance

Mixins promote composition over inheritance, which is generally considered a better design practice. Instead of creating deep inheritance hierarchies, mixins allow you to compose functionality.

Why not just use a class

A class would need multiple inheritance

If we used only classes without mixins, we would need multiple inheritance to achieve the same functionality, which Dart doesn't support:

// Without mixins, we'd want this (but it's not allowed):
class MyWidget extends StatelessWidget, AnimationSupport, LoggingSupport {}

Implementing an interface is not code reuse

While Dart allows implementing multiple interfaces, interfaces only define what methods a class should have, not how they are implemented:

class A {
  void methodA() { print("Method A"); }
}

class B implements A {
  @override
  void methodA() {
    // We have to reimplement the method
    print("Method A");
  }
}

With mixins, the implementation is reused:

mixin A {
  void methodA() { print("Method A"); }
}

class B with A {
  // Gets methodA implementation from mixin A
}

How mixins differ from classes

  1. Declaration: Mixins are declared using the mixin keyword instead of class
  2. Instantiation: Mixins cannot be instantiated on their own (new MyMixin() is invalid)
  3. Usage: Mixins are applied using the with keyword rather than extends
  4. Constraints: Mixins can be restricted to work only with specific class types using the on keyword

Restricting a mixin with on

The on keyword restricts a mixin so it can only be applied to a specific class or its subclasses.

class Animal {
  void eat() {
    print("Eating");
  }
}

mixin Swimmer on Animal {
  void swim() {
    print("Swimming");
  }
}

class Fish extends Animal with Swimmer {
  // Valid
}

Swimmer can only be mixed into Animal or a subclass of Animal. This fails to compile because Car does not extend Animal:

class Car with Swimmer {
  // Compile-time error
}

Why use on

Use it when the mixin depends on methods or properties of a particular base class.

mixin Logger on Animal {
  void logEating() {
    eat(); // Safe because Logger requires Animal
  }
}

Without on Animal, Dart cannot guarantee that eat() exists on the class using the mixin.

In short: on restricts a mixin to specific classes or their subclasses. Use it when the mixin depends on functionality provided by the target class.

Iterables vs lists

Both represent a sequence of elements. The difference is when those elements exist.

The difference

Iterable

An Iterable is an abstract class in Dart that represents a collection of elements that can be accessed sequentially. Key characteristics include:

  • It's a base interface that defines a way to access a sequence of elements
  • You can't instantiate an Iterable directly (abstract class)
  • It provides methods like map(), where(), reduce(), and forEach()
  • It's lazy: operations on iterables don't execute until you actually iterate through the elements
  • Elements are computed on-demand when accessed
  • It doesn't necessarily store all elements in memory at once
// Example of working with an Iterable
Iterable<int> generateNumbers() sync* {
  for (int i = 0; i < 10; i++) {
    yield i;  // Elements are generated one at a time
  }
}

void main() {
  final numbers = generateNumbers();
  final evenNumbers = numbers.where((num) => num % 2 == 0);
  // No computation has happened yet until we iterate

  for (var num in evenNumbers) {
    print(num);  // Computation happens here, on-demand
  }
}

List

A List is a concrete implementation of Iterable with the following characteristics:

  • It's an ordered collection that stores elements in a specific sequence
  • Elements can be accessed by index using [] operator
  • It has a fixed length or can be dynamic (growable)
  • All elements are stored in memory at once
  • Provides additional methods like add(), removeAt(), insert()
  • You can directly instantiate a List
void main() {
  // Creating a List
  List<int> numbers = [1, 2, 3, 4, 5];

  // Accessing by index
  print(numbers[2]);  // 3

  // Adding elements
  numbers.add(6);

  // Methods that modify the list
  numbers.remove(3);
  numbers.insert(0, 0);
}

Side by side

  1. Instantiation: You can directly create a List but not an Iterable (abstract)
  2. Memory Usage: List stores all elements in memory while Iterable can generate elements on-demand
  3. Operations: List has methods to modify the collection (add, remove), while Iterable primarily provides methods for traversal and transformation
  4. Random Access: List supports accessing elements by index, while Iterable only supports sequential access
  5. Evaluation: List operations are evaluated immediately, while Iterable operations are lazy (evaluated when needed)

When to use which

  1. Performance Optimization: Using Iterable for large datasets or streams of data can improve app performance
  2. Memory Management: Working with large collections efficiently
  3. Reactive Programming: Working with streams and asynchronous data flows
  4. Functional Programming: Using higher-order functions like map, where, and reduce

For example, when displaying a large list of transactions in a finance app, you might use Iterable operations to filter and transform the data before converting to a List for display.

On this page