Listview not updating after database update and adapter.notifyDataSetChanged(); - android

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

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);
}

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();
}

Related

CustomCursorLoader class does not refresh cursor on button click

I have written a program to add mobile no into my sqlite database on a button click which is working properly , I am also using a listview to show the data added for which I am using a CustomCursorLoader class to query my results .
The problem which I am facing is , suppose I have nothing in my database so the cursor count is 0 but when I insert a data for the first time , the cursor count should become 1 but it shows 0 , and then again when I insert another data at that moment i am getting cursor count as 1 but the data which was previously inserted is being shown in the listview
Posting my code
public class Home_Page extends Activity implements
LoaderManager.LoaderCallbacks<Cursor> {
DriverStatusAdapter driverStatusAdapter;
ListView listDriverId;
private static final int URL_LOADER = 0;
CustomCursorLoader loader = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
try{
dbListHelper = new DriverSqliteHelper(getBaseContext());
dbListHelper.open(getBaseContext());
}catch (Exception e){
e.printStackTrace();
}
String[] columns = new String[]
{DriverSqliteHelper.DbListHelper.DRIVER_USER_ID};
int[] to = new int[]{R.id.DriverId};
driverStatusAdapter = new DriverStatusAdapter(getBaseContext(),
R.layout.view_userid_item,null,columns,to,0);
listDriverId = (ListView) findViewById(R.id.driverIDList);
listDriverId.setAdapter(driverStatusAdapter);
registerForContextMenu(listDriverId);
Log.i("LoaderManager", "Started on activity start");
getLoaderManager().initLoader(0, null, Home_Page.this);
txtAdd.setOnClickListener(new View.OnClickListener() {
String userId = edtUserId.getText().toString();
if (userId.equals(""))
{
Snackbar snackbar = Snackbar.make(coordinatorLayout, "Please
enter user id", Snackbar.LENGTH_LONG);
View sbView = snackbar.getView();
TextView textView = (TextView)
sbView.findViewById(android.support.design.R.id.
snackbar_text);
snackbar.show();
}
else{
sendUserStatus(); ///// method to send mobile no to server
//// if status received from server is ok then i am inserting
////the data into the database
Log.i("LoaderManager", "Restarted on button click");
getLoaderManager().restartLoader(0, null, Home_Page.this);
}
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
switch (i){
case URL_LOADER:
Log.i("Case URL Loader", "Custom Cursor Loader called");
loader = new CustomCursorLoader(getBaseContext());
return loader;
default:
Log.i("Case default", "Default Case called");
return null;
}
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
Log.i("LoaderManager", "Finished load entry... - Cursor: " +
cursor.getCount());
this.loader = (CustomCursorLoader)loader;
driverStatusAdapter.changeCursor(cursor);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
Log.i("LoaderManager", "Resetting loader...");
driverStatusAdapter.changeCursor(null);
}
}
CustomCursorLoader.java
public class CustomCursorLoader extends CursorLoader{
Context context;
DriverSqliteHelper driverSqliteHelper;
Cursor cursor;
public CustomCursorLoader(Context context) {
super(context);
try {
driverSqliteHelper = new DriverSqliteHelper(context);
driverSqliteHelper.open(context);
}catch (Exception e){
e.printStackTrace();
}
}
public Cursor loadInBackground(){
cursor = driverSqliteHelper.getDriverStatus();
return cursor;
}
}
My Logcat
I/LoaderManager﹕ Started on activity start
I/Case URL Loader﹕ Custom Cursor Loader called
I/LoaderManager﹕ Finished load entry... - Cursor: 2
********on my first button click ********
I/LoaderManager﹕ Restarted on button click
I/Case URL Loader﹕ Custom Cursor Loader called
I/LoaderManager﹕ Finished load entry... - Cursor: 2
********* on my second button click ********
I/LoaderManager﹕ Restarted on button click
I/Case URL Loader﹕ Custom Cursor Loader called
I/LoaderManager﹕ Finished load entry... - Cursor: 3
I want my cursor count to change on first button click itself , can anyone suggest me what changes do i need to make ?
Ok i have found the solution myself , i put the getLoaderManager().restartLoader(0, null, Home_Page.this); inside sendUserStatus() method where i am also inserting the data.
Now the cursor count is incrementing and the listview is also getting updated automcatically

android cursor adapter list view

I have a view with a button and a list view backed by a cursor adapter containing bindView() and newView() for customized views. Each row of a list contains a Text and a checkbox. The data for each view comes from the database. I'm passing my Database adapter in the cursor adapter constructor. This I use to update the database when a checkbox is check or unchecked (works well). Of course I run "re-query" on cursor and view.refreshDrawableState()). Is this a good idea? What would be a better solution?
Second problem more serious, when a Button is clicked it starts a new activity. After hitting the back button from the new activity I get back my list View. But when I try to click on the checkbox this time I get Database close exception. Why? How do I fix this error?
Following is the list view and code snippet.
Button --------> Starts a new activity
CheckBox | TextView
CheckBox | TextView
MyActivity.java
onCreate() {
...
Button add_item_btn = (Button) findViewById(R.id.add_item_btn_id);
add_item_btn.setOnclickListener(new OnClickListener() {
//Start a new activity
});
}
protected void onPause() {
adapter.close();
mCursor.close();
}
protected void onResume() {
mListView = getListView();
adapter = new DBAdapter(getApplication());
adapter.open();
mCursor = adapter.getAllItems();
mCustomAdapter = new MyCursorAdapter(MyActivity.this, mCursor, adapter);
mListView.setAdapter(mCustomAdapter);
}
MyCursorAdapter.java
public class MyCursorAdapter extends CursorAdapter {
Cursor mCursor;
DBAdapter adapter;
public MyCursorAdapter(Context context, Cursor c, DBAdapter _adapter) {
...
mCursor = c;
adapter = _adapter;
}
public void bindView(final View view, Context context, final Cursor cursor) {
final CheckBox itemStatusCB = (CheckBox)
view.findViewById(R.id.item_status_id);
idx = cursor.getColumnIndex(myItem.ITEM_STATUS);
final long itemStatus = cursor.getLong(idx);
if (itemStatus == 1) {
itemStatusCB.setChecked(true);
} else {
itemStatusCB.setChecked(false);
}
itemStatusCB.setOnClickListener(new OnClickListener() {
#Override public void onClick(View v) {
int newStatus = 0;
if (((CheckBox) v).isChecked()) {
newStatus = 1;
}
adapter.updateItemStatus(itemId, newStatus);
mCursor.requery();
view.refreshDrawableState();
});
}
}
}
I was able to solve this. The new activity which was called had a DB connection open on onStart() and DB close on onDestroy(). After returning from that activity I was getting Database Illegal state Exception error as described with stack trace. I think it was returning cached version of DB connection. Once I removed DB.close() from the guest activity, it stopped issuing database not open error. Normally you would think that every activity can open a DB connection in it's onResume() or onStart() and close it in it's onPause() or onStop() or onDestroy() and it won't affect the connection across activities. Does this Make sense?

Attemp to re-open an already closed object android.database.sqlite

i have a Main activity which has a list view.
the listview items are loaded with LoaderManager.
when i click an item in the listview i open another activity that shows more information
(with "startActivityForResult")
the problem is :
when i go back(using the return key on) from the activity that shows information to the main activity and then click again i get an exception - Attempt to re-open an already closed object.
but if i go back from that activity(that shows more information) with a Button that i made there(which is actually "finish()") to the main activity and then cllick again then i get no exception
anyone knows what is the problem?
Thanks !
private void display_listview()
{
// create an adapter from the SimpleCursorAdapter
dataAdapter = new SimpleCursorAdapter(
this,
R.layout.row_invite_layout,
null,
columns,
to,
0);
// Assign adapter to ListView
listView.setAdapter(dataAdapter);
//Ensures a loader is initialized and active.
getSupportLoaderManager().initLoader(0, null, this);
//add implementation to listview item click
listView.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
Cursor cursor = (Cursor) listView.getItemAtPosition(position);
IInvite invite = LoadFromCursor(cursor);
cursor.close();
mUpdateFlag = true;
if(!mTrashFlag) // Trash Can is turned off
{
Intent intent = new Intent(getApplicationContext(), InviteViewActivity.class);
intent.putExtra("invite",(Invite)invite);
startActivityForResult(intent, INVITE_REQUEST_ID);
}
else // Trash Can is turned on
{
getContentResolver().delete(InviteContentProvider.CONTENT_URI, SQLdataHelper.KEY_ROWID+"="+invite.getID(), null);
ExtraDelete(invite);
getSupportLoaderManager().getLoader(0).forceLoad();
}
}
});
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data)
{
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
dataAdapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader)
{
dataAdapter.swapCursor(null);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args)
{
String[] projection = {
SQLdataHelper.KEY_ROWID,
SQLdataHelper.INVITE_CLIENT_ID,
SQLdataHelper.INVITE_CREATION_DATE,
SQLdataHelper.INVITE_NAME,
SQLdataHelper.INVITE_NOTE,
SQLdataHelper.INVITE_REQUESTED_DATE,
SQLdataHelper.INVITE_TOTAL_PRICE,
SQLdataHelper.INVITE_STATUS
};
CursorLoader cursorLoader = new CursorLoader(this,
InviteContentProvider.CONTENT_URI, projection, null, null, null);
return cursorLoader;
}
UPDATE: FIXED IT. i added this method to my main activity..
#Override
public void onResume()
{
super.onResume();
getSupportLoaderManager().getLoader(0).forceLoad();
}
You can do it by creating public static fields, but I wouldn't recomend it. You can store data in shared preferences and then retrieve it whenever you want.

SimpleCursorAdapter how to show an image?

I am making an android recipes application where i use a database.
In database there is a column named" "images", where i store the name of the file of the picture of the recipe where i store at drawable folder.
Now i want to make a List with the recipes, showing:
1) the Title of the recipe
2) a short description
and
3) an image of the recipe
To do that i use a Simplecursoradaptor.
My problem is i can not show the image.
I want to read the file name from the column "images" and then set the image at my imageview (R.id.imageview1)
Here is my code untill now:
public class RecipesMainActivity extends Activity
{
public static final String ROW_ID = "row_id"; //Intent extra key
private ListView esodaListView; // the ListActivity's ListView
private SimpleCursorAdapter esodaAdapter; // adapter for ListView
DatabaseConnector databaseConnector = new DatabaseConnector(RecipesMainActivity.this);
// called when the activity is first created
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recipes_main);
esodaListView = (ListView)findViewById(R.id.esodaList);
esodaListView.setOnItemClickListener(viewEsodaListener);
databaseConnector.open();
// map each esoda to a TextView in the ListView layout
// The desired columns to be bound
String[] from = new String[] {"title","ingredients","image"}; // built an String array named "from"
//The XML defined views which the data will be bound to
int[] to = new int[] { R.id.esodaTextView, R.id.amountTextView, R.id.imageView1}; // built an int array named "to"
// EsodaMainActivity.this = The context in which the ListView is running
// R.layout.esoda_list_item = Id of the layout that is used to display each item in ListView
// null =
// from = String array containing the column names to display
// to = Int array containing the column names to display
esodaAdapter = new SimpleCursorAdapter (this, R.layout.recipe_list_item, null, from, to);
esodaListView.setAdapter(esodaAdapter); // set esodaView's adapter
} // end of onCreate method
#Override
protected void onResume()
{
super.onResume(); // call super's onResume method
// create new GetEsodaTask and execute it
// GetEsodaTask is an AsyncTask object
new GetEsodaTask().execute((Object[]) null);
} // end of onResume method
// onStop method is executed when the Activity is no longer visible to the user
#Override
protected void onStop()
{
Cursor cursor= esodaAdapter.getCursor(); // gets current cursor from esodaAdapter
if (cursor != null)
cursor.deactivate(); // deactivate cursor
esodaAdapter.changeCursor(null); // adapter now has no cursor (removes the cursor from the CursorAdapter)
super.onStop();
} // end of onStop method
// this class performs db query outside the GUI
private class GetEsodaTask extends AsyncTask<Object, Object, Cursor>
{
// we create a new DatabaseConnector obj
// EsodaMainActivity.this = Context
DatabaseConnector databaseConnector = new DatabaseConnector(RecipesMainActivity.this);
// perform the db access
#Override
protected Cursor doInBackground(Object... params)
{
databaseConnector.open();
// get a cursor containing call esoda
return databaseConnector.getAllEsoda();
// the cursor returned by getAllContacts() is passed to method onPostExecute()
} // end of doInBackground method
// here we use the cursor returned from the doInBackground() method
#Override
protected void onPostExecute(Cursor result)
{
esodaAdapter.changeCursor(result); // set the adapter's Cursor
databaseConnector.close();
} // end of onPostExecute() method
} // end of GetEsodaTask class
I searched a lot online but i couldnt find something tha could help me.
Can i set an image at an imageview with simplecursoradaptor or not?
Do i have to make a custom cursor adaptor? And if i have to make a custom one how can i do it?
You need to set the ViewBinder of the adapter to set the image of the ImageView to the value received from the DB.
esodaAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
#Override
public boolean setViewValue (View view, Cursor cursor, int columnIndex){
if (view.getId() == R.id.imageView1) {
ImageView IV=(ImageView) view;
int resID = getApplicationContext().getResources().getIdentifier(cursor.getString(columnIndex), "drawable", getApplicationContext().getPackageName());
IV.setImageDrawable(getApplicationContext().getResources().getDrawable(resID));
return true;
}
return false;
}

Refresh a SimpleCursorAdapter after performing work on a non-UI thread

I'm trying to call .notifyDataSetChange() on a SimpleCursorAdapter displayed in a ListView from an XML-parsing non-UI thread and can't for the life of me figure out how. I've searched all over and all I've found are articles that talk about refreshing from within the ListView itself, which I'm not doing. I can't figure out a way to pass in the adapter or get it from the parent or whatever I need to do to call a method on it from another thread.
The ListView will update fine the next time I launch the activity, but I want it to refresh as soon as the XML parsing is done so that the user will see the new data immediately.
The answer's probably simple; it's just eluding me. Thanks in advance for any help!
Here's my code:
public class DirectoryListActivity extends DirectoryActivity {
public final Handler mHandler = new Handler();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.directory_list);
// Populate the ListView
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(
directoryPeople.PEOPLE_TABLE
);
String asColumnsToReturn[] = {
//snip
};
mCursor = queryBuilder.query(mDB, asColumnsToReturn, null, null,
null, null, directoryPeople.DEFAULT_SORT_ORDER);
startManagingCursor(mCursor);
// HERE'S THE ADAPTER
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.directory_people_item, mCursor,
new String[]{
//snip
new int[]{
//snip
);
ListView av = (ListView)findViewById(R.id.listPeople);
av.setAdapter(adapter);
//Perform sync in background
startXMLParseThread();
}
public void startXMLParseThread() {
new Thread () {
boolean success = false;
public void run() {
try {
// XML-Parsing and Table-Updating code
} catch (Exception e) {
success = false;
}
mHandler.post(new Runnable() {
public void run() {
TextView txtUpdateStatus = (TextView)findViewById(R.id.txtUpdateStatus);
if (success) {
txtUpdateStatus.setText(R.string.synced);
} else {
txtUpdateStatus.setText(R.string.sync_failed);
}
adapter.notifyDataSetChanged(); // ECLIPSE HATES
}
});
}
}.start();
}
}
}
No need to create a new adapter...
.notifyDataSetChanged() should be called only in case the data rows actually changed (inserted or deleted rows), in case you just updated the values on rows a simple call to requery() on your cursor should be enough:
adapter.getCursor().requery();
Edit: by your comment I see that you have in fact a compilation problem...
You must declare the adapter as a class member (before/after mHandler declare it: private SimpleCursorAdapter adapter)
Then when you initialize it, replace
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.directory_people_item, mCursor,
new String[]{
//snip
new int[]{
//snip
);
with:
adapter = new SimpleCursorAdapter(this,
R.layout.directory_people_item, mCursor,
new String[]{
//snip
new int[]{
//snip
);
Create a new adapter when the query is finished and set it your listview to give a general idea below is an example of gridview change it accordingly to suit your needs
globalAdapter = new GridImageAdapter(getApplicationContext());
globalAdapter.notifyDataSetChanged();
grid.setAdapter(globalAdapter);
grid.invalidateViews();

Categories

Resources