Dart ListMixin
last modified April 4, 2025
ListMixin is a mixin class in Dart that provides a basic implementation of the List interface. It helps create custom list-like classes with minimal effort.
By implementing just a few required methods, ListMixin provides all standard List operations. It's useful when you need custom list behavior with storage.
Basic ListMixin Implementation
Here's a minimal implementation of a custom list using ListMixin.
import 'dart:collection'; class CustomList<E> with ListMixin<E> { final List<E> _list = []; @override int get length => _list.length; @override set length(int newLength) { _list.length = newLength; } @override E operator [](int index) => _list[index]; @override void operator []=(int index, E value) { _list[index] = value; } } void main() { var myList = CustomList<int>(); myList.addAll([1, 2, 3]); print(myList); // [1, 2, 3] }
We create CustomList that mixes in ListMixin. We implement four required methods: length getter/setter and index operators. ListMixin provides the rest.
$ dart main.dart [1, 2, 3]
Fixed-Size List with ListMixin
We can create a fixed-size list by controlling the length setter.
import 'dart:collection'; class FixedList<E> with ListMixin<E> { final List<E> _list; FixedList(int length) : _list = List.filled(length, null as E); @override int get length => _list.length; @override set length(int newLength) { throw UnsupportedError('Cannot change length of fixed list'); } @override E operator [](int index) => _list[index]; @override void operator []=(int index, E value) { _list[index] = value; } } void main() { var fixed = FixedList<String>(3); fixed[0] = 'Apple'; fixed[1] = 'Banana'; fixed[2] = 'Cherry'; print(fixed); try { fixed.add('Date'); // Will throw } catch (e) { print(e); // Unsupported operation: Cannot add to a fixed list } }
The FixedList throws when trying to modify its length. This prevents adding or removing elements while allowing index access and modification.
$ dart main.dart [Apple, Banana, Cherry] Unsupported operation: Cannot add to a fixed list
Validating List Elements
ListMixin can help create lists that validate elements before adding them.
import 'dart:collection'; class PositiveIntList with ListMixin<int> { final List<int> _list = []; @override int get length => _list.length; @override set length(int newLength) { _list.length = newLength; } @override int operator [](int index) => _list[index]; @override void operator []=(int index, int value) { if (value <= 0) throw ArgumentError('Value must be positive'); _list[index] = value; } @override void add(int value) { if (value <= 0) throw ArgumentError('Value must be positive'); super.add(value); } } void main() { var positives = PositiveIntList(); positives.addAll([1, 2, 3]); print(positives); try { positives.add(-5); // Will throw } catch (e) { print(e); // Invalid argument: Value must be positive } }
PositiveIntList validates that all elements are positive integers. We override the add method and index setter to enforce this constraint.
$ dart main.dart [1, 2, 3] Invalid argument: Value must be positive
Observable List with ListMixin
We can create an observable list that notifies listeners of changes.
import 'dart:collection'; class ObservableList<E> with ListMixin<E> { final List<E> _list = []; final List<void Function()> _listeners = []; void addListener(void Function() listener) { _listeners.add(listener); } void _notify() { for (var listener in _listeners) { listener(); } } @override int get length => _list.length; @override set length(int newLength) { _list.length = newLength; _notify(); } @override E operator [](int index) => _list[index]; @override void operator []=(int index, E value) { _list[index] = value; _notify(); } @override void add(E value) { super.add(value); _notify(); } } void main() { var obsList = ObservableList<String>(); obsList.addListener(() => print('List changed: $obsList')); obsList.add('First'); obsList.addAll(['Second', 'Third']); obsList[1] = 'Updated'; }
ObservableList notifies listeners whenever the list changes. We override mutating operations to trigger notifications after changes occur.
$ dart main.dart List changed: [First] List changed: [First, Second, Third] List changed: [First, Updated, Third]
Lazy Loading List with ListMixin
ListMixin can help implement lists that load elements on demand.
import 'dart:collection'; class LazyList<E> with ListMixin<E> { final List<E?> _list; final E Function(int index) _loader; LazyList(int length, this._loader) : _list = List.filled(length, null); @override int get length => _list.length; @override set length(int newLength) { throw UnsupportedError('Cannot change length of lazy list'); } @override E operator [](int index) { if (_list[index] == null) { _list[index] = _loader(index); } return _list[index] as E; } @override void operator []=(int index, E value) { _list[index] = value; } } void main() { var lazy = LazyList<int>(5, (index) { print('Loading element $index'); return index * 10; }); print('List created'); print('Accessing index 2: ${lazy[2]}'); print('Accessing index 4: ${lazy[4]}'); print('Accessing index 2 again: ${lazy[2]}'); }
LazyList loads elements only when they're first accessed. The loader function creates elements on demand, caching them for future access.
$ dart main.dart List created Loading element 2 Accessing index 2: 20 Loading element 4 Accessing index 4: 40 Accessing index 2 again: 20
Best Practices
- Minimal Implementation: Implement only required methods for your needs.
- Performance: Consider overriding frequently used methods for optimization.
- Consistency: Ensure all list operations maintain your invariants.
- Documentation: Clearly document any behavior that differs from standard List.
Source
This tutorial covered Dart's ListMixin with practical examples demonstrating how to create custom list implementations efficiently.
Author
List all Dart tutorials.