Memento Pattern Dart Example: Snapshot Undo in Flutter
This memento pattern Dart example builds snapshot-based undo for a Flutter text editor with immutable state, a caretaker history, and redo — and shows exactly when memento beats a command history.
Undo is the feature users assume exists until the moment it doesn't. They type a paragraph, hit a wrong button, and their hand flies to Ctrl+Z on pure faith — faith your app must earn. This memento pattern Dart example earns it the honest way: instead of reverse-engineering what "undo" means for every edit, the editor takes a sealed snapshot of its state before each change, handing it to a caretaker. Undo becomes "restore the last snapshot." No inverse operations, no broken encapsulation, no edge cases where an action's reverse isn't its opposite. We'll build it in Dart 3.13 with immutable snapshots, add a redo stack with the invalidation rule editors follow, and settle the big decision: snapshots (memento) or action history (command)? What you'll learn The three roles — originator, caretaker, memento — and why the caretaker must never peek inside Why naive undo (storing raw state or writing inverse operations) breaks down Immutable snapshots with copyWith : snapshots that can't be corrupted after the fact A caretaker with undo AND redo stacks, including the "fresh edit clears redo" rule A decision table: memento vs command history The four mistakes that turn an undo stack into a memory leak All code targets Dart 3.13 (ships with Flutter 3.47 ) and runs with plain dart run . The naive undo that breaks The first attempt at undo usually looks like this: keep the old values around, and write code that puts them back. For a text editor with content, cursor position, and formatting, that means the caretaker reaches into the editor's internals: // Naive: the history manager knows the editor's private fields. class NaiveHistory { final _contents = <String>[]; final _cursors = <int>[]; void save(String content, int cursor) {…