PageSourceSearch

https://asyncredux.com/assets/js/b58cff2b.215be860.js

js asyncredux.com collected 2026-10-07 19:27:37 UTC 6,105 bytes, 1 lines download raw bytes

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.