Search code examples
androidserializationundo-redo

android best way to implement undo/redo on ArrayList


sorry about the general nature of this question but i don't want to start with any assumptions for fear of missing the big picture

i have a document editing kind of app (music notation) and want to implement undo and redo. all the relevant data is held in this

static  ArrayList <TTEvt> mEvList;

in my windows/MFC app, i just serialize the data structure and put it on a stack. it uses lots of memory but easy and foolproof.

so i was wondering what's the bast way to save and restore my ArrayList in android?

thanks


Solution

  • so here is the working multilevel undo/redo code. it works fine but it is sloooow. i took the gzip out which helps a bit but it basically makes my UI unusable. but at least it works, i can try optimize from here.

        static LinkedList<byte[]> undoStack = new LinkedList<byte[]>();
        static LinkedList<byte[]> redoStack = new LinkedList<byte[]>();
    
        public static void addUndoCheckpoint() {
    
            long start = System.currentTimeMillis();
    
            byte[] byteBuf = null;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
                ObjectOutputStream objectOut = new ObjectOutputStream(baos);
                for (TTEvt ev : Doc.mEvList)
                    objectOut.writeObject(ev);
                objectOut.close();
                byteBuf = baos.toByteArray();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            undoStack.push(byteBuf);
    
            if (undoStack.size() > 10)
                undoStack.removeLast(); // limit size
    
            redoStack.clear();
    
            long end = System.currentTimeMillis();
            Log.d("MainView", "addUndoCheckpoint time=" + (end - start) + "mS");
        }
    
        public static void doUndo() {
    
            if (undoStack.size() == 0)
                return;
    
            // push current state onto redo stack first
            byte[] byteBuf = null;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
                ObjectOutputStream objectOut = new ObjectOutputStream(baos);
                for (TTEvt ev : Doc.mEvList)
                    objectOut.writeObject(ev);
                objectOut.close();
                byteBuf = baos.toByteArray();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            redoStack.push(byteBuf); // push current state onto redo stack
            if (redoStack.size() > 10)
                redoStack.removeLast();
    
            // now undo
            mEvList.clear();
            byteBuf = undoStack.pop();
    
            ObjectInputStream objectIn = null;
            try {
                ByteArrayInputStream bais = new ByteArrayInputStream(byteBuf);
                // GZIPInputStream gzipIn;
                // gzipIn = new GZIPInputStream(bais);
                objectIn = new ObjectInputStream(bais);
                while (true) {
                    TTEvt ev = (TTEvt) objectIn.readObject();
                    if (ev == null)
                        break;
                    Doc.mEvList.add(ev);
                }
                objectIn.close();
            } catch (IOException e) {
                // this is the normal exit
                if (objectIn != null) {
                    try {
                        objectIn.close();
                    } catch (IOException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                }
                // e.printStackTrace();
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            MainView.forceTotalRedraw();
        }
    
        public static void doRedo() {
    
            if (redoStack.size() == 0)
                return;
    
            // push current state onto undo stack first so we can undo the redo
            byte[] byteBuf = null;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
                ObjectOutputStream objectOut = new ObjectOutputStream(baos);
                for (TTEvt ev : Doc.mEvList)
                    objectOut.writeObject(ev);
                objectOut.close();
                byteBuf = baos.toByteArray();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            undoStack.push(byteBuf); // push current state onto redo stack
            if (undoStack.size() > 10)
                undoStack.removeLast();
    
            // now redo
            mEvList.clear();
            byteBuf = redoStack.pop();
    
            ObjectInputStream objectIn = null;
            try {
                ByteArrayInputStream bais = new ByteArrayInputStream(byteBuf);
                // GZIPInputStream gzipIn;
                // gzipIn = new GZIPInputStream(bais);
                objectIn = new ObjectInputStream(bais);
                while (true) {
                    TTEvt ev = (TTEvt) objectIn.readObject();
                    if (ev == null)
                        break;
                    Doc.mEvList.add(ev);
                }
                objectIn.close();
            } catch (IOException e) {
                // this is the normal exit
                if (objectIn != null) {
                    try {
                        objectIn.close();
                    } catch (IOException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                }
                // e.printStackTrace();
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            MainView.forceTotalRedraw();
    
        }
    }
    

    note that your should call addUndoCheckpoint BEFORE you change ArrayList. i'm currently working on a version that you can call after you change ArrayList. this has the advantage that you can do addUndoCheckpoint in the background and not slow down the UI.