ZetCode

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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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

Source

Dart UnmodifiableListMixin Documentation

This tutorial covered Dart's UnmodifiableListMixin with practical examples demonstrating how to create immutable list implementations.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.