Listview not updating after database update and adapter.notifyDataSetChanged();
Asked Answered
H

2

1

I was browsing the net for 2 days allready and tryed alot of stuff but can't seem to figure out what is wrong with this.

I am still fairly new to the Android deevelopment so I probably missed something obvious.

I have an app witch is using a sqllite databse to store some data and for the porpose of this Proof of concept displaying that in a listview. I can add items to the list, delete them.

So far so good. The problem I have is when I instead of delete update a column in the databse called "deleted" and set it to 1 and then have the adapter to update the list. It seems not to work.

If I use the delete statement it works. It updates and everything is fine but I whant to have the deleted items in the database but not to show them (So basicly "hiding" items)

If I check the database the update itself succeded the column changes and everything so I guess it is a refresh problem because the adapter does not requery the database or something in that direction

Listview Loader:

public void fillData() {

    if(lw.getAdapter() == null){
        // Fields from the database (projection)
        // Must include the _id column for the adapter to work
        String[] from = new String[] { TodoTable.COLUMN_SUMMARY, TodoTable.COLUMN_ID};
        String where = TodoTable.COLUMN_DELETED + " = ?";


        Cursor cursor = getContentResolver().query(TodoContentProvider.CONTENT_URI,from,where,new String[] {"0"},null);
        // Fields on the UI to which we map
        int[] to = new int[] { R.id.label };

        adapter = new SimpleCursorAdapter(this, R.layout.todo_row, cursor, from,
                to, 0);
        Log.v("Count",Integer.toString(cursor.getCount()));
        lw.setAdapter(adapter);
    }
    else
        adapter.notifyDataSetChanged();
    }

Delete functon

@Override
public boolean onContextItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case DELETE_ID:
            /* Code for actual delete
            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item
                    .getMenuInfo();
            Uri uri = Uri.parse(TodoContentProvider.CONTENT_URI + "/"
                    + info.id);
            getContentResolver().delete(uri, null, null);
            fillData();
            */

            /* Code for update and hide */
            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item
                    .getMenuInfo();
            Uri uri = Uri.parse(TodoContentProvider.CONTENT_URI + "/"
                    + info.id);
            ContentValues values = new ContentValues();
            values.put(TodoTable.COLUMN_DIRTY, 1);
            values.put(TodoTable.COLUMN_DELETED, 1);
            getContentResolver().update(uri,values,null,null);
            fillData();
            return true;
    }
    return super.onContextItemSelected(item);
}

if I put a log to the ContentProvider's query function it actually does not fire.

Any suggestions on how to figure this out?

If I use adapter.swapCursor(cursor); it works fine just just don't know if this is the correct way of doing this.

public void fillData() {

    // Fields from the database (projection)
    // Must include the _id column for the adapter to work
    String[] from = new String[] { TodoTable.COLUMN_SUMMARY, TodoTable.COLUMN_ID};
    String where = TodoTable.COLUMN_DELETED + " = ?";

    Cursor cursor = getContentResolver().query(TodoContentProvider.CONTENT_URI,from,where,new String[] {"0"},null);

    // Fields on the UI to which we map
    int[] to = new int[] { R.id.label };

    if(lw.getAdapter() == null){
        adapter = new SimpleCursorAdapter(this, R.layout.todo_row, cursor, from,
                to, 0);
        Log.v("Count",Integer.toString(cursor.getCount()));
        lw.setAdapter(adapter);
    }
    else
    {
        adapter.swapCursor(cursor);
    }
}

Ty for the help

Hyder answered 29/10, 2013 at 10:58 Comment(2)
if you use a well behaving ContentProvider and CursorLoader you dont need to call notifyDataSetChanged() fillData() etc, see Notepad ContentProviderSniperscope
Using restartLoader() is not formally correct here. What you need to do is for your content provder to correctly call setNotificationUri() on cursor before it returns it. And from then one on each update, delete, insert call getContext().getContentResolver().notifyChange() for that same uri. Even if you modify your data outside of content provider, calling notifyChange() should notify all cursors with that notification uri and trigger a new cursor loading.Liman
H
9

Using adapter.swapCursor(cursor) is correct so you're almost there in answering your own question.

Your first piece of code doesn't work because when you call fillData() after your database update, you simply call adapter.notifyDataSetChanged() and the dataset hasn't actually changed because the cursor is the same. A cursor is a reference to rows from your database and updating the underlying database doesn't refresh the cursor. Your second piece of code does refresh the cursor and swaps the new one in to the adapter (which also triggers an update to the view it is bound to).

The more common way to code this is:

Add this interface to your activity:

public class MyActivity extends Activity implementsLoaderManager.LoaderCallbacks<Cursor>

In onCreate, set up the adapter (note that the cursor is null at this point):

String[] from = new String[] { TodoTable.COLUMN_SUMMARY, TodoTable.COLUMN_ID};
int[] to = new int[] { R.id.label };
adapter = new SimpleCursorAdapter(this, R.layout.todo_row, null, from, to, 0);  //Note that the cursor is null
lw.setAdapter(adapter);

Initiate the loader:

getLoaderManager().initLoader(0, null, this);

This calls onCreateLoader in a background thread (so if your query is long running it won't block the UI thread). When it finishes, onLoadFinished is called on the UI thread where you can swap in the new cursor.

After you do a delete or update, restart the loader:

getLoaderManager().restartLoader(0, null, this);

This calls onLoaderReset which removes the existing cursor from the adapter and then calls onCreateLoader again to swap in a new one.

Finally add these methods:

public Loader<Cursor> onCreateLoader(int id, Bundle args)
{
    String[] from = new String[] { TodoTable.COLUMN_SUMMARY, TodoTable.COLUMN_ID};
    String where = TodoTable.COLUMN_DELETED + " = ?";

    Loader<Cursor> loader = new CursorLoader(this, TodoContentProvider.CONTENT_URI, from, where, new String[] {"0"}, null);     
    return loader;
}

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
{
    adapter.swapCursor(cursor);
}

public void onLoaderReset(Loader<Cursor> loader)
{
    adapter.swapCursor(null);
}
Heterogenous answered 29/10, 2013 at 11:53 Comment(2)
Worked for me! But the only thing I had to do extra was to forceLoad on init and restart.Fundamental
Its really simple and helpful, but my doubt is how to do this in the case if you have implemented custom baseadapter to populate your listview?Linctus
G
3

Here below is my working solution. Briefly, I am updating the underlying database in a service and when the service finishes its job it calls the activity with a localbroadcastmanager. I use List and BaseAdapter.

In the service, I call:

    Intent intent = new Intent("notifyactivity");
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

In the activity:

    @Override
        public void onResume() {
        super.onResume();
       LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,new IntentFilter("notifyactivity"));
}

private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {         
      applicationcontacts.clear(); //clear the list first
      applicationcontacts.addAll(db.getAllContacts()); //reload the list
      listview=(ListView) findViewById(R.id.listview1);
      listview.setAdapter(listadaptor);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                listadaptor.notifyDataSetChanged();
            }
        });
  }
};

@Override
protected void onPause() {
  LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
  super.onPause();
}
Greylag answered 17/4, 2015 at 7:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.