New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DragSortListView.RemoveListener not working with SimpleDragSortCursorAdapter #85

Closed
jmmcreynolds opened this Issue Mar 14, 2013 · 12 comments

Comments

Projects
None yet
2 participants
@jmmcreynolds

jmmcreynolds commented Mar 14, 2013

Maybe I'm missing something with this, but in trying to figure out how to get this to work with a database, I started playing around with the demo that's available. What I ended up doing is just modifying the CursorDSLV class to get it to pull info from my database. Now I'm trying to figure out how to actually remove the item from the database once it is flung away from the view. I figured that this would be accomplished by setting up a DragSortListView.RemoveListener, but when I fling to remove the item this never gets called. As you can see below, it should log some info after you remove an item, but it doesn't log anything. It correctly logs info when the profile is called though.

Am I doing something wrong or not understanding how this should be implemented?

Here's the code I'm using (yes, I purposely changed the layout to warp_main so that you could fling to remove):

public class CursorDSLV extends FragmentActivity {

private SimpleDragSortCursorAdapter adapter;
private DatabaseAdapter dbHelper;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.warp_main);

    dbHelper = new DatabaseAdapter(this);
    dbHelper.openConnection();

    // The desired columns to be bound
    String[] cols = new String[] { DatabaseAdapter.ITEM_NAME,
            DatabaseAdapter.ITEM_POSITION };

    // the XML defined views that the data will be bound to
    int[] ids = new int[] { R.id.text, R.id.item_position_list };

    // pull all items from database
    dbHelper.addDummyRecords(30);
    Cursor cursor = dbHelper.getAllItemRecords();

    adapter = new MAdapter(this, R.layout.list_item_click_remove, null,
            cols, ids, 0);

    DragSortListView dslv = (DragSortListView) findViewById(android.R.id.list);

    dslv.setRemoveListener(onRemove);
    dslv.setDragScrollProfile(ssProfile);
    dslv.setAdapter(adapter);

    adapter.changeCursor(cursor);

}

private DragSortListView.RemoveListener onRemove = new DragSortListView.RemoveListener() {
    @Override
    public void remove(int which) {
        Log.i("onRemove", "remove method hit");
        // adapter.remove(adapter.getItem(which));
    }
};

private DragSortListView.DragScrollProfile ssProfile = new DragSortListView.DragScrollProfile() {
    @Override
    public float getSpeed(float w, long t) {
        Log.i("ssProfile", "getSpeed method hit");
        if (w > 0.8f) {
            // Traverse all views in a millisecond
            return ((float) adapter.getCount()) / 0.001f;
        } else {
            return 10.0f * w;
        }
    }
};

private class MAdapter extends SimpleDragSortCursorAdapter {

    public MAdapter(Context ctxt, int rmid, Cursor c, String[] cols,
            int[] ids, int something) {
        super(ctxt, rmid, c, cols, ids, something);
        mContext = ctxt;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = super.getView(position, convertView, parent);
        return v;
    }
}

@Override
public void onDestroy() {
    super.onDestroy();
    if (dbHelper != null) {
        dbHelper.closeConnection();
    }
}
}
@jmmcreynolds

This comment has been minimized.

Show comment
Hide comment
@jmmcreynolds

jmmcreynolds Mar 14, 2013

Hmmm....looks like this is brought up in issue #44 (Comment: #44 (comment)). I thought there would be an easy way to handle deletes with the cursors, but it looks like once the user is done modifying the view you have to compare the cursors and then find out what was removed and also update positions for items in the database. Am I off base here, or is there an easier/different way to handle deletes?

jmmcreynolds commented Mar 14, 2013

Hmmm....looks like this is brought up in issue #44 (Comment: #44 (comment)). I thought there would be an easy way to handle deletes with the cursors, but it looks like once the user is done modifying the view you have to compare the cursors and then find out what was removed and also update positions for items in the database. Am I off base here, or is there an easier/different way to handle deletes?

@bauerca

This comment has been minimized.

Show comment
Hide comment
@bauerca

bauerca Mar 14, 2013

Owner

You are correct. The developer using DSLV has to persist the reorderings/deletions him/herself.

There are several reasons for passing this responsibility onto the dslv user. 1. DSLV doesn't know when the user would like to persist the changes. 2. DSLV doesn't know how to persist the changes (b/c a Cursor is just a view into some datastore. e.g. is the Cursor from a db, a 2d array, a .csv, etc?).

I should also mention that the use of remove/drop listeners with a subclass of DragSortCursorAdapter is redundant. The DragSortCursorAdapter implements the listener interfaces (which are detected when setAdapter() is called on DSLV). If you'd like additional behavior on removals or drops, you should override the remove() and drop() methods in your MAdapter class.

Owner

bauerca commented Mar 14, 2013

You are correct. The developer using DSLV has to persist the reorderings/deletions him/herself.

There are several reasons for passing this responsibility onto the dslv user. 1. DSLV doesn't know when the user would like to persist the changes. 2. DSLV doesn't know how to persist the changes (b/c a Cursor is just a view into some datastore. e.g. is the Cursor from a db, a 2d array, a .csv, etc?).

I should also mention that the use of remove/drop listeners with a subclass of DragSortCursorAdapter is redundant. The DragSortCursorAdapter implements the listener interfaces (which are detected when setAdapter() is called on DSLV). If you'd like additional behavior on removals or drops, you should override the remove() and drop() methods in your MAdapter class.

@bauerca bauerca closed this Mar 14, 2013

@jmmcreynolds

This comment has been minimized.

Show comment
Hide comment
@jmmcreynolds

jmmcreynolds Mar 15, 2013

Thanks, that's what I figured. As for overriding remove() and drop() in MAdapter, I think that would be like re-inventing the wheel.

What I ended up doing was comparing the ListView cursor with a cursor that contained the original items from the database. The position value that is stored in the database is updated, and then the items that had been removed form the ListView are deleted from the database. There are probably better ways to go about doing this, and I realize that for large lists this could really be slow, but it works for me now.

Since I wasn't able to find anything like this anywhere, I figured I'd post it here. I have a "Commit" button that basically runs an AsyncTask with the code listed below. I plan on writing up something on my blog in the next week or so too. Anyway, here's what I came up with:

ArrayList<Integer> itemIds = new ArrayList<Integer>();
Cursor loadItems = dbAdapter.getAllItemRecords();
while (loadItems.moveToNext()) {
    int rowId = loadItems.getInt(loadItems.getColumnIndex("_id"));
    itemIds.add(rowId);
}

for (int i = 0; i < dslvListView.getCount(); i++) {
    Cursor infoToDisplay = (Cursor) dslvListView.getItemAtPosition(i);
    int rowId = infoToDisplay.getInt(infoToDisplay.getColumnIndex("_id"));
    dbAdapter.updateItemPosition(rowId, i);

    // iterate through original ids and remove ids that exist
    for (int ii = 0; ii < itemIds.size(); ii++) {
        if (itemIds.get(ii).equals(rowId)) {
            itemIds.remove(ii);
        }
    }
}

// itemIds should now only contain ids that need to be removed
for (int i = 0; i < itemIds.size(); i++) {
    // remove items from the database that were removed from ListView
    dbAdapter.deleteItemRecord(itemIds.get(i));
}

Thanks again!

jmmcreynolds commented Mar 15, 2013

Thanks, that's what I figured. As for overriding remove() and drop() in MAdapter, I think that would be like re-inventing the wheel.

What I ended up doing was comparing the ListView cursor with a cursor that contained the original items from the database. The position value that is stored in the database is updated, and then the items that had been removed form the ListView are deleted from the database. There are probably better ways to go about doing this, and I realize that for large lists this could really be slow, but it works for me now.

Since I wasn't able to find anything like this anywhere, I figured I'd post it here. I have a "Commit" button that basically runs an AsyncTask with the code listed below. I plan on writing up something on my blog in the next week or so too. Anyway, here's what I came up with:

ArrayList<Integer> itemIds = new ArrayList<Integer>();
Cursor loadItems = dbAdapter.getAllItemRecords();
while (loadItems.moveToNext()) {
    int rowId = loadItems.getInt(loadItems.getColumnIndex("_id"));
    itemIds.add(rowId);
}

for (int i = 0; i < dslvListView.getCount(); i++) {
    Cursor infoToDisplay = (Cursor) dslvListView.getItemAtPosition(i);
    int rowId = infoToDisplay.getInt(infoToDisplay.getColumnIndex("_id"));
    dbAdapter.updateItemPosition(rowId, i);

    // iterate through original ids and remove ids that exist
    for (int ii = 0; ii < itemIds.size(); ii++) {
        if (itemIds.get(ii).equals(rowId)) {
            itemIds.remove(ii);
        }
    }
}

// itemIds should now only contain ids that need to be removed
for (int i = 0; i < itemIds.size(); i++) {
    // remove items from the database that were removed from ListView
    dbAdapter.deleteItemRecord(itemIds.get(i));
}

Thanks again!

@bauerca

This comment has been minimized.

Show comment
Hide comment
@bauerca

bauerca Mar 15, 2013

Owner

This method was designed to make your life easier. For example:

private class MAdapter extends SimpleDragSortListAdapter {
    // ...

    public void persistChanges() {
        Cursor c = getCursor();
        c.moveToPosition(-1);
        while (c.moveToNext()) {
            int listPos = getListPosition(c.getPosition());
            if (listPos == DragSortListAdapter.REMOVED) {
                dbAdapter.deleteItemRecord(c.getInt(c.getColumnIndex("_id")));
            } else if (listPos != c.getPosition()) {
                dbAdapter.updateItemPosition(c.getInt(c.getColumnIndex("_id")), listPos);
            }
        }
    }
}
Owner

bauerca commented Mar 15, 2013

This method was designed to make your life easier. For example:

private class MAdapter extends SimpleDragSortListAdapter {
    // ...

    public void persistChanges() {
        Cursor c = getCursor();
        c.moveToPosition(-1);
        while (c.moveToNext()) {
            int listPos = getListPosition(c.getPosition());
            if (listPos == DragSortListAdapter.REMOVED) {
                dbAdapter.deleteItemRecord(c.getInt(c.getColumnIndex("_id")));
            } else if (listPos != c.getPosition()) {
                dbAdapter.updateItemPosition(c.getInt(c.getColumnIndex("_id")), listPos);
            }
        }
    }
}
@jmmcreynolds

This comment has been minimized.

Show comment
Hide comment
@jmmcreynolds

jmmcreynolds Mar 15, 2013

Ahhh...that's much simpler and more efficient. However, I'm trying to implement it and I'm not having much luck. Where should persistChanges() get called from? I created the method like you have above but I'm not sure exactly where it should be called from in the code I originally posted. If I put it in getView, right before "return v;", it works, but it gets called every time anything happens to the list and bogs interaction down. I tried just overriding remove() and drop() and putting persistChanges(); in those, but that didn't work at all (list view wasn't updated and items weren't removed). I kind of figured it wouldn't work, because there's no logic to actually update the list view. I apologize for my lack of knowledge about all this, but do appreciate your help and direction.

jmmcreynolds commented Mar 15, 2013

Ahhh...that's much simpler and more efficient. However, I'm trying to implement it and I'm not having much luck. Where should persistChanges() get called from? I created the method like you have above but I'm not sure exactly where it should be called from in the code I originally posted. If I put it in getView, right before "return v;", it works, but it gets called every time anything happens to the list and bogs interaction down. I tried just overriding remove() and drop() and putting persistChanges(); in those, but that didn't work at all (list view wasn't updated and items weren't removed). I kind of figured it wouldn't work, because there's no logic to actually update the list view. I apologize for my lack of knowledge about all this, but do appreciate your help and direction.

@bauerca

This comment has been minimized.

Show comment
Hide comment
@bauerca

bauerca Mar 15, 2013

Owner

No problem!

I would call this at the activity level. For example:

public class MyActivity extends ListActivity {
    // ...

    @Override
    protected void onPause() {
        super.onPause();
        mAdapter.persistChanges();
    }
}
Owner

bauerca commented Mar 15, 2013

No problem!

I would call this at the activity level. For example:

public class MyActivity extends ListActivity {
    // ...

    @Override
    protected void onPause() {
        super.onPause();
        mAdapter.persistChanges();
    }
}
@jmmcreynolds

This comment has been minimized.

Show comment
Hide comment
@jmmcreynolds

jmmcreynolds Mar 15, 2013

Makes perfect sense. I think my head is about to explode though. In order to get it to work with my example I had to modify it a little. If I tried it like you have above, I got a "Cannot make a static reference to the non-static method" error. This is what worked for me:

@Override
protected void onPause() {
    super.onPause();
    ((MAdapter) adapter).persistChanges();
}

I really appreciate all of your help with this!

jmmcreynolds commented Mar 15, 2013

Makes perfect sense. I think my head is about to explode though. In order to get it to work with my example I had to modify it a little. If I tried it like you have above, I got a "Cannot make a static reference to the non-static method" error. This is what worked for me:

@Override
protected void onPause() {
    super.onPause();
    ((MAdapter) adapter).persistChanges();
}

I really appreciate all of your help with this!

@bauerca

This comment has been minimized.

Show comment
Hide comment
@bauerca

bauerca Mar 15, 2013

Owner

Sorry about that, I had assumed:

private MAdapter mAdapter = new MAdapter(...);

;)

Happy to hear it's working.

Owner

bauerca commented Mar 15, 2013

Sorry about that, I had assumed:

private MAdapter mAdapter = new MAdapter(...);

;)

Happy to hear it's working.

@bauerca

This comment has been minimized.

Show comment
Hide comment
@bauerca

bauerca Mar 15, 2013

Owner

I should have written: mMAdapter.persistChanges(). Ha.

Owner

bauerca commented Mar 15, 2013

I should have written: mMAdapter.persistChanges(). Ha.

@jmmcreynolds

This comment has been minimized.

Show comment
Hide comment
@jmmcreynolds

jmmcreynolds Mar 15, 2013

Hehe, goes both ways. ;) I should have declared that differently...correctly.

For anyone that's interested in the final working code for the modified CursorDSLV demo that I hacked together, you can find it below. Remember, this is just demo code. You might want to use a button, or something else, to actually commit the changes, rather than have them automatically committed. This gives the user an "out" in case they made changes they don't want saved. To do so, you would just move the call to mMAdapter.persistChanges(); to whatever way you choose to commit the changes.

public class CursorDSLV extends FragmentActivity {

    private MAdapter mMAdapter;
    private DatabaseAdapter mDbHelper;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.warp_main);

        mDbHelper = new DatabaseAdapter(this);
        mDbHelper.openConnection();

        // The desired columns to be bound
        String[] cols = new String[] { DatabaseAdapter.ITEM_NAME,
                DatabaseAdapter.ITEM_POSITION };

        // the XML defined views that the data will be bound to
        int[] ids = new int[] { R.id.text, R.id.item_position_list };

        // pull all items from database
        Cursor cursor = mDbHelper.getAllItemRecords();

        mMAdapter = new MAdapter(this, R.layout.list_item_handle_right, null,
                cols, ids, 0);

        DragSortListView dslv = (DragSortListView) findViewById(android.R.id.list);

        // set dslv profile for faster scroll speeds
        dslv.setDragScrollProfile(ssProfile);

        dslv.setAdapter(mMAdapter);
        mMAdapter.changeCursor(cursor);

    }

    private DragSortListView.DragScrollProfile ssProfile = new DragSortListView.DragScrollProfile() {
        @Override
        public float getSpeed(float w, long t) {
            if (w > 0.8f) {
                // Traverse all views in a millisecond
                return ((float) mMAdapter.getCount()) / 0.001f;
            } else {
                return 10.0f * w;
            }
        }
    };

    private class MAdapter extends SimpleDragSortCursorAdapter {

        public void persistChanges() {
            Cursor c = getCursor();
            c.moveToPosition(-1);
            while (c.moveToNext()) {
                int listPos = getListPosition(c.getPosition());
                if (listPos == REMOVED) {
                    mDbHelper.deleteItemRecord(c.getInt(c.getColumnIndex("_id")));
                } else if (listPos != c.getPosition()) {
                    mDbHelper.updateItemPosition(c.getInt(c.getColumnIndex("_id")), listPos);
                }
            }
        }

        public MAdapter(Context ctxt, int rmid, Cursor c, String[] cols,
                int[] ids, int something) {
            super(ctxt, rmid, c, cols, ids, something);
            mContext = ctxt;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = super.getView(position, convertView, parent);
            return v;
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        mMAdapter.persistChanges();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mDbHelper != null) {
            mDbHelper.closeConnection();
        }
    }
}

jmmcreynolds commented Mar 15, 2013

Hehe, goes both ways. ;) I should have declared that differently...correctly.

For anyone that's interested in the final working code for the modified CursorDSLV demo that I hacked together, you can find it below. Remember, this is just demo code. You might want to use a button, or something else, to actually commit the changes, rather than have them automatically committed. This gives the user an "out" in case they made changes they don't want saved. To do so, you would just move the call to mMAdapter.persistChanges(); to whatever way you choose to commit the changes.

public class CursorDSLV extends FragmentActivity {

    private MAdapter mMAdapter;
    private DatabaseAdapter mDbHelper;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.warp_main);

        mDbHelper = new DatabaseAdapter(this);
        mDbHelper.openConnection();

        // The desired columns to be bound
        String[] cols = new String[] { DatabaseAdapter.ITEM_NAME,
                DatabaseAdapter.ITEM_POSITION };

        // the XML defined views that the data will be bound to
        int[] ids = new int[] { R.id.text, R.id.item_position_list };

        // pull all items from database
        Cursor cursor = mDbHelper.getAllItemRecords();

        mMAdapter = new MAdapter(this, R.layout.list_item_handle_right, null,
                cols, ids, 0);

        DragSortListView dslv = (DragSortListView) findViewById(android.R.id.list);

        // set dslv profile for faster scroll speeds
        dslv.setDragScrollProfile(ssProfile);

        dslv.setAdapter(mMAdapter);
        mMAdapter.changeCursor(cursor);

    }

    private DragSortListView.DragScrollProfile ssProfile = new DragSortListView.DragScrollProfile() {
        @Override
        public float getSpeed(float w, long t) {
            if (w > 0.8f) {
                // Traverse all views in a millisecond
                return ((float) mMAdapter.getCount()) / 0.001f;
            } else {
                return 10.0f * w;
            }
        }
    };

    private class MAdapter extends SimpleDragSortCursorAdapter {

        public void persistChanges() {
            Cursor c = getCursor();
            c.moveToPosition(-1);
            while (c.moveToNext()) {
                int listPos = getListPosition(c.getPosition());
                if (listPos == REMOVED) {
                    mDbHelper.deleteItemRecord(c.getInt(c.getColumnIndex("_id")));
                } else if (listPos != c.getPosition()) {
                    mDbHelper.updateItemPosition(c.getInt(c.getColumnIndex("_id")), listPos);
                }
            }
        }

        public MAdapter(Context ctxt, int rmid, Cursor c, String[] cols,
                int[] ids, int something) {
            super(ctxt, rmid, c, cols, ids, something);
            mContext = ctxt;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = super.getView(position, convertView, parent);
            return v;
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        mMAdapter.persistChanges();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mDbHelper != null) {
            mDbHelper.closeConnection();
        }
    }
}
@bauerca

This comment has been minimized.

Show comment
Hide comment
@bauerca

bauerca Mar 15, 2013

Owner

Thanks for the contribution!

Owner

bauerca commented Mar 15, 2013

Thanks for the contribution!

@jmmcreynolds

This comment has been minimized.

Show comment
Hide comment
@jmmcreynolds

jmmcreynolds Mar 22, 2013

No problem. I thought I posted the following the other day, but I guess I didn't save it:

I put up a quick blog post about what I learned and how I got this to work. It includes code snippets and source code for a working demo app. Hope it helps someone else out.

Thanks for all the help!

jmmcreynolds commented Mar 22, 2013

No problem. I thought I posted the following the other day, but I guess I didn't save it:

I put up a quick blog post about what I learned and how I got this to work. It includes code snippets and source code for a working demo app. Hope it helps someone else out.

Thanks for all the help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment