Dart UnmodifiableListMixin
last modified April 4, 2025
The UnmodifiableListMixin in Dart provides a way to create immutable lists. It mixes in unmodifiable behavior to any List implementation.
When using UnmodifiableListMixin, any attempt to modify the list will throw an UnsupportedError. This is useful for creating read-only views of lists.
Basic UnmodifiableListMixin Usage
Here's how to create a simple unmodifiable list by mixing in the class.
import 'dart:collection'; class UnmodifiableList<E> extends ListBase<E> with UnmodifiableListMixin { final List<E> _source; UnmodifiableList(this._source); @override int get length => _source.length; @override E operator [](int index) => _source[index]; } void main() { var numbers = [1, 2, 3]; var unmodifiable = UnmodifiableList(numbers); print(unmodifiable); // [1, 2, 3] try { unmodifiable.add(4); // Throws UnsupportedError } catch (e) { print('Error: $e'); } }
We create a custom UnmodifiableList class that wraps a source list. The mixin provides all modification methods that throw UnsupportedError. We only need to implement length and [].
$ dart main.dart [1, 2, 3] Error: Unsupported operation: Cannot modify an unmodifiable list
Creating an UnmodifiableListView
Dart provides UnmodifiableListView as a convenience wrapper using this mixin.
import 'dart:collection'; void main() { var fruits = ['apple', 'banana', 'orange']; var unmodifiableFruits = UnmodifiableListView(fruits); print(unmodifiableFruits); try { unmodifiableFruits[0] = 'pear'; // Throws } catch (e) { print('Cannot modify: $e'); } // Original list can still be modified fruits.add('pear'); print(unmodifiableFruits); // Shows the change }
UnmodifiableListView creates a view that prevents modifications. Note that changes to the original list are still visible through the view.
$ dart main.dart [apple, banana, orange] Cannot modify: Unsupported operation: Cannot modify an unmodifiable list [apple, banana, orange, pear]
Custom Unmodifiable List with Additional Methods
We can extend the basic implementation with custom methods that don't modify.
import 'dart:collection'; class SafeList<E> extends ListBase<E> with UnmodifiableListMixin { final List<E> _items; SafeList(this._items); @override int get length => _items.length; @override E operator [](int index) => _items[index]; // Custom safe methods E get firstOrNull => isEmpty ? null : _items.first; E safeElementAt(int index) => (index >= 0 && index < length) ? _items[index] : null; } void main() { var safeList = SafeList<String>(['a', 'b', 'c']); print(safeList.firstOrNull); // a print(safeList.safeElementAt(5)); // null print(safeList.safeElementAt(1)); // b try { safeList.add('d'); // Throws } catch (e) { print('Modification prevented'); } }
This SafeList adds safe access methods while maintaining immutability. The mixin ensures all modification methods throw errors while allowing read operations.
$ dart main.dart a null b Modification prevented
Combining with Other Mixins
UnmodifiableListMixin can be combined with other mixins for more functionality.
import 'dart:collection'; class ObservableList<E> extends ListBase<E> with UnmodifiableListMixin, ChangeNotifier { final List<E> _source; ObservableList(this._source); @override int get length => _source.length; @override E operator [](int index) { notifyListeners(); return _source[index]; } } void main() { var obsList = ObservableList<int>([1, 2, 3]); obsList.addListener(() => print('List accessed')); print(obsList[0]); // Prints message and returns 1 print(obsList[1]); // Prints message and returns 2 try { obsList.add(4); // Still throws } catch (e) { print('Cannot modify observable list'); } }
Here we combine UnmodifiableListMixin with ChangeNotifier. The list remains unmodifiable but now notifies listeners on access. This shows the mixin's flexibility.
$ dart main.dart List accessed 1 List accessed 2 Cannot modify observable list
Implementing a Truly Immutable List
For complete immutability, we can combine the mixin with a private constructor.
import 'dart:collection'; class ImmutableList<E> extends ListBase<E> with UnmodifiableListMixin { final List<E> _items; // Private constructor ImmutableList._(this._items); // Factory constructor for creation factory ImmutableList.of(Iterable<E> items) => ImmutableList._(List<E>.unmodifiable(items)); @override int get length => _items.length; @override E operator [](int index) => _items[index]; } void main() { var immutable = ImmutableList.of([10, 20, 30]); print(immutable); // [10, 20, 30] try { immutable.removeAt(0); // Throws } catch (e) { print('Cannot modify immutable list'); } // Original list changes don't affect our immutable list var source = [1, 2, 3]; var immut = ImmutableList.of(source); source.add(4); print(immut); // Still [1, 2, 3] }
This implementation ensures complete immutability by creating an unmodifiable copy of the source list. The private constructor prevents external modification.
$ dart main.dart [10, 20, 30] Cannot modify immutable list [1, 2, 3]
Best Practices
- Document Immutability: Clearly document when a list is unmodifiable.
- Defensive Copies: Consider making defensive copies for true immutability.
- Use Views Wisely: UnmodifiableListView reflects source changes.
- Error Handling: Handle UnsupportedError when passing to APIs.
- Performance: Unmodifiable lists have minimal overhead.
Source
Dart UnmodifiableListMixin Documentation
This tutorial covered Dart's UnmodifiableListMixin with practical examples demonstrating how to create immutable list implementations.
Author
List all Dart tutorials.