gwtindexoutofboundsexceptiongwt-editors

How do I allow a single-item/sub-editor GWT Editor to remove itself from a ListEditor that contains it? e.g. on remove button clicked


While trying to use GWT's ListEditor system I was unable to find a working example where the UI of each item in the list had a delete/remove button.

The examples I found were all like this one[1] and have an EditorSource.create() implementation creates the per-item Editor and appears to wire up a handler to remove the item from the underlying list via listEditor.getList().remove(index).

However, the anonymous implementation of the deletion handler closes around the value of index at the time of the sub-editor's creation, this leads to IndexOutOfBoundExceptions or the wrong item being removed as each removal changes the index of all items that come after it.

I pulled my hair out for a while trying to see what I was missing in the examples that kept that from happening, but from what I could tell they really did all have that problem, so while the fix is fairly simple, I will still post it here so there is at least one example people can find that does item removal correctly.

[1] I think all the examples I found were all derived from the one I linked, although that one in particular had a little more logic in remove() and may have been doing something to avoid the problem like correcting the list order somehow, I haven't dug into the other code in that project.


Solution

  • The following is a minimal ListEditor example that corrects the problem found in other examples.

    public abstract class FooEditor extends Composite implements Editor<Foo> {
    
        Widget root; // Instantiated explicitly or through uibinder
    
        // Implemented as one of uibinder+fields, fields, methods, or LeafValueEditor.set/getValue()
    
        public FooEditor() { 
            initWidget(root); 
        }
    
        // Used for brevity, could be any triggering mechanism, click handler, event handler, etc.
        abstract void onDeleteClicked(); 
    }
    
    public class FooListEditor extends Composite implements IsEditor<ListEditor<Foo, FooEditor>> {
    
        private class FooEditorSource extends EditorSource<FooEditor> {
    
            @Override 
            public FooEditor create(int index) {
    
                FooEditor subEditor = new FooEditor()
                {
                    @Override
                    public void onDeleteClicked()
                    {
                        // =======================================================
                        //
                        // This fixes the problem present in other examples
                        // by determining the current index at the time of removal
                        //
                        // =======================================================
                        int currentIndex = listEditor.getEditors().indexOf(this);
                        listEditor.getList().remove(currentIndex);    
                    }
                };
    
                setIndex(subEditor, index);
    
                return subEditor;
            }
    
            @Override 
            public void dispose(FooEditor subEditor) { 
                subEditor.removeFromParent(); 
            }
    
            @Override 
            public void setIndex(FooEditor subEditor, int index) {
                listPanel.insert(subEditor, index);
            }
        }
    
        FlowPanel listPanel; // Instantiated explicitly or through uibinder
    
        ListEditor<Foo, FooEditor> listEditor = ListEditor.of(new FooEditorSource());
    
        public FooListEditor() {
            initWidget(listPanel);
        }
    
        @Override 
        public ListEditor<Foo, FooEditor> asEditor() { 
            return listEditor; 
        }
    }