1"use strict";(self.webpackChunkasyncredux_com=self.webpackChunkasyncredux_com||[]).push([[3803],{4259:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>i,default:()=>u,frontMatter:()=>a,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"miscellaneous/undo-and-redo","title":"Undo and Redo","description":"AsyncRedux makes it easy to add undo and redo features to your app.","source":"@site/flutter/miscellaneous/undo-and-redo.md","sourceDirName":"miscellaneous","slug":"/miscellaneous/undo-and-redo","permalink":"/flutter/miscellaneous/undo-and-redo","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":20,"frontMatter":{"sidebar_position":20},"sidebar":"tutorialSidebar","previous":{"title":"Streams and Timers","permalink":"/flutter/miscellaneous/streams-and-timers"},"next":{"title":"Where to put business logic?","permalink":"/flutter/miscellaneous/business-logic"}}');var o=t(4848),s=t(8453);const a={sidebar_position:20},i="Undo and Redo",d={},c=[{value:"Example Implementation",id:"example-implementation",level:2},{value:"1. Create the UndoRedoObserver",id:"1-create-the-undoredoobserver",level:3},{value:"2. Register the Observer with the Store",id:"2-register-the-observer-with-the-store",level:3},{value:"3. Create Undo and Redo Actions",id:"3-create-undo-and-redo-actions",level:3},{value:"4. Use in Your UI",id:"4-use-in-your-ui",level:3}];function l(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,s.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"undo-and-redo",children:"Undo and Redo"})}),"\n",(0,o.jsx)(n.p,{children:"AsyncRedux makes it easy to add undo and redo features to your app.\nThe approach is straightforward:"}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsx)(n.li,{children:"Create a state observer to save states to a history list"}),"\n",(0,o.jsx)(n.li,{children:"Create actions to navigate through that history (undo/redo)"}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["This also works to undo/redo only ",(0,o.jsx)(n.strong,{children:"part"})," of the state. Your observer can save only that part,\nand your actions can revert only that part."]}),"\n",(0,o.jsx)(n.h2,{id:"example-implementation",children:"Example Implementation"}),"\n",(0,o.jsx)(n.h3,{id:"1-create-the-undoredoobserver",children:"1. Create the UndoRedoObserver"}),"\n",(0,o.jsx)(n.p,{children:"The observer saves each state change to a history list:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-dart",children:"class UndoRedoObserver implements StateObserver<AppState> {\n final List<AppState> _history = [];\n int _currentIndex = -1;\n \n // Maximum number of states to keep in history\n final int maxHistorySize;\n \n UndoRedoObserver({this.maxHistorySize = 50}); \n \n void observe(\n ReduxAction<AppState> action,\n AppState prevState,\n AppState newState,\n Object? error,\n int dispatchCount,\n ) {\n // Don't save states from undo/redo actions themselves\n if (action is UndoAction || action is RedoAction) return;\n \n // If state didn't change, don't add to history\n if (identical(prevState, newState)) return;\n \n // Remove any \"future\" states if we're not at the end\n if (_currentIndex < _history.length - 1) {\n _history.removeRange(_currentIndex + 1, _history.length);\n }\n\n // Add the new state\n _history.add(newState);\n _currentIndex = _history.length - 1;\n \n // Limit history size\n if (_history.length > maxHistorySize) {\n _history.removeAt(0);\n _currentIndex--;\n }\n }\n\n bool get canUndo => _currentIndex > 0;\n \n bool get canRedo => _currentIndex < _history.length - 1;\n \n AppState? getPreviousState() {\n if (!canUndo) return null;\n _currentIndex--;\n return _history[_currentIndex];\n }\n\n AppState? getNextState() {\n if (!canRedo) return null;\n _currentIndex++;\n return _history[_currentIndex];\n }\n}\n"})}),"\n",(0,o.jsx)(n.h3,{id:"2-register-the-observer-with-the-store",children:"2. Register the Observer with the Store"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-dart",children:"final undoRedoObserver = UndoRedoObserver();
1\n\nvar store = Store<AppState>(\n initialState: AppState.initialState(),\n stateObservers: [undoRedoObserver],\n);\n"})}),"\n",(0,o.jsx)(n.h3,{id:"3-create-undo-and-redo-actions",children:"3. Create Undo and Redo Actions"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-dart",children:"class UndoAction extends ReduxAction<AppState> { \n AppState? reduce() {\n AppState? previousState = undoRedoObserver.getPreviousState();\n return previousState ?? null;\n }\n}\n\nclass RedoAction extends ReduxAction<AppState> { \n AppState? reduce() {\n AppState? nextState = undoRedoObserver.getNextState();\n return nextState ?? null;\n }\n}\n"})}),"\n",(0,o.jsx)(n.h3,{id:"4-use-in-your-ui",children:"4. Use in Your UI"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-dart",children:"class UndoRedoButtons extends StatelessWidget { \n Widget build(BuildContext context) {\n return Row(\n children: [\n IconButton(\n icon: Icon(Icons.undo),\n onPressed: () => context.dispatch(UndoAction()), \n ),\n IconButton(\n icon: Icon(Icons.redo),\n onPressed: () => context.dispatch(RedoAction()),\n ),\n ],\n );\n }\n}\n"})})]})}function u(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(l,{...e})}):l(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>a,x:()=>i});var r=t(6540);const o={},s=r.createContext(o);function a(e){const n=r.useContext(s);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),r.createElement(s.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.