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 classesMixins 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
- Declaration: Mixins are declared using the
mixinkeyword instead ofclass - Instantiation: Mixins cannot be instantiated on their own (
new MyMixin()is invalid) - Usage: Mixins are applied using the
withkeyword rather thanextends - Constraints: Mixins can be restricted to work only with specific class types using the
onkeyword
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
Iterabledirectly (abstract class) - It provides methods like
map(),where(),reduce(), andforEach() - 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
- Instantiation: You can directly create a
Listbut not anIterable(abstract) - Memory Usage:
Liststores all elements in memory whileIterablecan generate elements on-demand - Operations:
Listhas methods to modify the collection (add, remove), whileIterableprimarily provides methods for traversal and transformation - Random Access:
Listsupports accessing elements by index, whileIterableonly supports sequential access - Evaluation:
Listoperations are evaluated immediately, whileIterableoperations are lazy (evaluated when needed)
When to use which
- Performance Optimization: Using
Iterablefor large datasets or streams of data can improve app performance - Memory Management: Working with large collections efficiently
- Reactive Programming: Working with streams and asynchronous data flows
- Functional Programming: Using higher-order functions like
map,where, andreduce
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.