Understanding the transaction system for safe document updates
Transactions are the recommended way to modify documents in AppFlowy Editor. They ensure consistency, enable undo/redo functionality, and support collaborative editing.
A Transaction is a collection of operations that will be applied to the document atomically. All document mutations should go through transactions.
class Transaction { final Document document; final List<Operation> operations; Selection? beforeSelection; Selection? afterSelection;}
Never modify the document directly. Always use transactions to ensure proper state management, undo/redo support, and collaborative editing compatibility.
class UpdateOperation extends Operation { final Path path; final Attributes attributes; // New attributes final Attributes oldAttributes; // Previous attributes (for undo)}
class UpdateTextOperation extends Operation { final Path path; final Delta delta; // Changes to apply final Delta inverted; // Inverse changes (for undo)}
final leftNode = editorState.getNodeAtPath([0]);final rightNode = editorState.getNodeAtPath([1]);if (leftNode != null && rightNode != null) { transaction.mergeText( leftNode, rightNode, leftOffset: leftNode.delta?.length, // Merge at end of left rightOffset: 0, // Start of right );}
final transaction = editorState.transaction;// beforeSelection is set to current selection automaticallytransaction.insertText(node, 0, 'Hello');// afterSelection is automatically set to Position after insertionawait editorState.apply(transaction);// Selection is updated to afterSelection
Transactions automatically compose operations for efficiency:
final transaction = editorState.transaction;// These operations on the same node are composedtransaction.insertText(node, 0, 'Hello');transaction.insertText(node, 5, ' world');// Results in a single UpdateTextOperation
final transaction = editorState.transaction;transaction.insertText(node, 0, 'A');transaction.insertText(node, 1, 'B');transaction.insertText(node, 2, 'C');// All composed into one UpdateTextOperationawait editorState.apply(transaction);
Operations are transformed to handle path changes:
final transaction = editorState.transaction;// Insert at [0]transaction.insertNode([0], newNode);// This operation's path is automatically transformed to [1]transaction.deleteNodesAtPath([0]); // Actually deletes what was originally at [0], now at [1]
// Don't record in history (e.g., for temporary updates)await editorState.apply( transaction, options: const ApplyOptions( recordUndo: false, inMemoryUpdate: true, ),);
editorState.transactionStream.listen((value) { final (time, transaction, options) = value; if (time == TransactionTime.before) { // Transaction about to be applied print('Will apply ${transaction.operations.length} operations'); } else { // Transaction has been applied print('Applied ${transaction.operations.length} operations'); // Send to backend for collaborative editing sendToBackend(transaction.toJson()); }});
final transaction = editorState.transaction;// Multiple operations in one transactiontransaction.insertNode([0], paragraphNode(text: 'First'));transaction.insertNode([1], paragraphNode(text: 'Second'));transaction.insertNode([2], paragraphNode(text: 'Third'));// All applied atomicallyawait editorState.apply(transaction);