Dart UnmodifiableMapMixin
last modified April 4, 2025
The UnmodifiableMapMixin in Dart provides a way to create immutable map views. It prevents modifications to the underlying map by throwing exceptions.
This mixin implements all mutating operations to throw UnsupportedError. It's used by UnmodifiableMapView and other immutable map implementations.
Basic Usage of UnmodifiableMapMixin
Here's how to create a simple class that uses UnmodifiableMapMixin.
import 'dart:collection'; class ImmutableMap<K, V> with UnmodifiableMapMixin<K, V> { final Map<K, V> _map; ImmutableMap(this._map); @override V? operator [](Object? key) => _map[key]; @override Iterable<K> get keys => _map.keys; } void main() { var data = {'a': 1, 'b': 2, 'c': 3}; var immutable = ImmutableMap(data); print(immutable['a']); // 1 try { immutable['d'] = 4; // Throws } catch (e) { print('Error: $e'); } }
We create an ImmutableMap class that wraps a regular Map. The mixin prevents modifications while allowing read operations. Attempts to modify throw errors.
$ dart main.dart 1 Error: Unsupported operation: Cannot modify unmodifiable map
Creating UnmodifiableMapView
Dart provides UnmodifiableMapView which uses UnmodifiableMapMixin internally.
import 'dart:collection'; void main() { var original = {'name': 'Alice', 'age': 30}; var unmodifiable = UnmodifiableMapView(original); print(unmodifiable['name']); // Alice try { unmodifiable['age'] = 31; // Throws } catch (e) { print('Error: $e'); } // Changes to original reflect in view original['age'] = 31; print(unmodifiable['age']); // 31 }
UnmodifiableMapView provides a read-only view of the original map. Note that changes to the original map are visible in the view, as it's just a wrapper.
$ dart main.dart Alice Error: Unsupported operation: Cannot modify unmodifiable map 31
Implementing Custom Unmodifiable Map
Here's a more complete custom implementation using UnmodifiableMapMixin.
import 'dart:collection'; class SafeMap<K, V> with UnmodifiableMapMixin<K, V> { final Map<K, V> _source; SafeMap(Map<K, V> map) : _source = Map.of(map); @override V? operator [](Object? key) => _source[key]; @override Iterable<K> get keys => _source.keys; @override int get length => _source.length; @override bool containsKey(Object? key) => _source.containsKey(key); @override bool containsValue(Object? value) => _source.containsValue(value); } void main() { var data = {'x': 10, 'y': 20}; var safeMap = SafeMap(data); print(safeMap['x']); // 10 print(safeMap.containsKey('y')); // true try { safeMap.clear(); // Throws } catch (e) { print('Error: $e'); } }
This SafeMap class provides a complete immutable map implementation. It copies the source map to prevent external modifications from affecting the immutable view.
$ dart main.dart 10 true Error: Unsupported operation: Cannot modify unmodifiable map
Combining with Other Mixins
UnmodifiableMapMixin can be combined with other mixins for enhanced functionality.
import 'dart:collection'; class ReportingMap<K, V> with UnmodifiableMapMixin<K, V> { final Map<K, V> _map; int _accessCount = 0; ReportingMap(this._map); @override V? operator [](Object? key) { _accessCount++; print('Access #$_accessCount to key $key'); return _map[key]; } @override Iterable<K> get keys => _map.keys; } void main() { var map = ReportingMap({'a': 1, 'b': 2}); print(map['a']); print(map['b']); print(map['a']); try { map['c'] = 3; // Throws } catch (e) { print('Error: $e'); } }
This ReportingMap tracks access counts while remaining unmodifiable. It shows how to add functionality while maintaining immutability via the mixin.
$ dart main.dart Access #1 to key a 1 Access #2 to key b 2 Access #3 to key a 1 Error: Unsupported operation: Cannot modify unmodifiable map
Testing Unmodifiable Behavior
Here's how to verify all modification methods throw exceptions.
import 'dart:collection'; void testUnmodifiable(Map<String, int> map) { final methods = [ () => map['x'] = 1, () => map.clear(), () => map.remove('x'), () => map.putIfAbsent('x', () => 1), () => map.update('x', (v) => v + 1), ]; for (var method in methods) { try { method(); print('Method did not throw'); } catch (e) { print('Caught: ${e.runtimeType}'); } } } void main() { var unmodifiable = UnmodifiableMapView({'a': 1}); testUnmodifiable(unmodifiable); }
This test verifies that all mutating operations throw exceptions. The UnmodifiableMapMixin ensures consistent behavior across all methods.
$ dart main.dart Caught: UnsupportedError Caught: UnsupportedError Caught: UnsupportedError Caught: UnsupportedError Caught: UnsupportedError
Best Practices
- Immutable Data: Use for APIs that shouldn't modify input maps.
- Defensive Copies: Consider copying if original might change.
- Documentation: Clearly document that maps are unmodifiable.
- Performance: Wrapping is cheap but doesn't prevent source changes.
Source
Dart UnmodifiableMapMixin Documentation
This tutorial covered Dart's UnmodifiableMapMixin with practical examples demonstrating immutable map creation and usage patterns.
Author
List all Dart tutorials.