I'm currently facing a stupid issue. I've a listview with a custom adapter (extending CursorAdapter).
It displays a custom view with different buttons (share, like, delete). For the delete button, it has an alertdialog to confirm the removal of the item. When the user confirms, the item is deleted from the db. Everything works fine until here.
My question is, how can I update my listview efficiently with my new dataset?
Thanks a lot for your help.
Code:
public class CommentCursorAdapter extends CursorAdapter{
(...)
#Override
public void bindView(View view, Context arg1, Cursor cursor) {
(...)
holder.list_item_comment_discard_btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
final String _id = v.getTag().toString();
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle("Delete");
builder.setMessage("Do you want to delete "+_id);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User clicked OK button
DBAdapter dba = new DBAdapter(mActivity);
dba.open();
dba.remove(_id);
Log.i("TAAG", "removed: "+_id);
dba.close();
// How to update the listview ??
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
AlertDialog d = builder.create();
d.show();
}
});
holder.list_item_comment_discard_btn.setTag(_id);
(...)
}
(...)
}
If you use LoaderManager to manage the lifecycle of your cursors (that is the right way to do this) you need only to call restartLoader and then (re)set the cursor of your adapter in onLoadFinished. Your listview will be reloaded with updated data.
The management of a cursor in your activity or fragment should be done in a very simple and straightforward way:
In order to initialize your cursor:
public void onCreate(Bundle savedInstanceState) { // or onStart
// ...
this.getLoaderManager().initLoader(MY_CURSOR_ID, null, this);
// ...
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// TODO: create a cursor loader that will load your cursor here
}
public void onLoadFinished(Loader<Cursor> arg0, final Cursor arg1) {
// TODO: set your cursor as the cursor of your adapter here;
}
public void onLoaderReset(Loader<Cursor> arg0) {
// TODO: set null as the cursor of your adapter and 'free' all cursor
// references;
}
And then, when you need to reload your data:
this.getLoaderManager().restartLoader(MY_CURSOR_ID, null, this);
Call
cursor.requery();
notifyDataSetChanged();
.
If you want real performance use the AsyncTaskLoader.
Just requery the Cursor like this:
public void onClick(DialogInterface dialog, int id) {
// User clicked OK button
DBAdapter dba = new DBAdapter(mActivity);
dba.open();
dba.remove(_id);
Log.i("TAAG", "removed: "+_id);
dba.close();
cursor.requery();
notifyDataSetChanged();
}
and then call notifyDataSetChanged() to tell the Adapter that the data has changed, and it has to build the adapter again.
This will be an intense operation if the requery takes long. Consider doing it in another thread.
After you delete the item in the DB, call notifyDataSetChanged on your CommentCursorAdapter, granted with this anonymous inner class you are using you will need a final reference to this since this in inside your CursorAdapter. This should trigger a refresh of your ListView.
http://developer.android.com/reference/android/widget/BaseAdapter.html#notifyDataSetChanged()
Related
In my main fragment, I have a listView called notesListView. noteAdapter populates notesListView. When user long clicks on one of the notesListView's elements, a dialog shows up and asks if user really wants to remove an item. If he agrees, then that item is removed from the database. If not - then life goes on.
The issue is that my Dialog is other class (other Fragment). For this class, I pass my database object and noteAdapter object as well, so it could remove item from database and then notify noteAdapter that data has changed. Sounds good enough, but it doesn't work, and I have absolutely no idea why. Give it a look please and help me out.
This is a method in mainFragment, which handles the mentioned listView:
public void handleNotes(final ListView notesListView) {
if (database.getNoteCount() != 0) {
notesListView.setAdapter(noteAdapter);
notesListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
TextView textViewId = (TextView) view.findViewById(R.id.textViewId);
DeleteNoteFragment newFragment = new DeleteNoteFragment(database, noteAdapter, Integer.parseInt(textViewId.getText().toString()));
newFragment.show(getActivity().getSupportFragmentManager(), "deleteConfirmation");
return false;
}
});
}
}
As you can see, DeleteNoteFragment is being created and then shown.
Lets look at DeleteNoteFragment itself:
public class DeleteNoteFragment extends DialogFragment {
private Database database;
private NoteAdapter noteAdapter;
private int i;
public DeleteNoteFragment(Database database, NoteAdapter noteAdapter, int i) {
this.database = database;
this.noteAdapter = noteAdapter;
this.i = i;
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.dialog_delete_note)
.setPositiveButton(R.string.dialog_delete_confirm, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
database.removeNote(i);
noteAdapter.notifyDataSetChanged();
Toast.makeText(getActivity(), "Note deleted successfully!", Toast.LENGTH_LONG).show();
}
})
.setNegativeButton(R.string.dialog_delete_denny, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
// Create the AlertDialog object and return it
return builder.create();
}
}
Maybe you can spot where I am making a mistake, or got any tips how to solve this issue?
You are deleting the data in database but not in the adapter:
database.removeNote(i);
noteAdapter.notifyDataSetChanged();
Creates a method in the adapter to get the list that you have in the adapter. Something like:
database.removeNote(i);
noteAdapter.getListOfItems().remove(i);
noteAdapter.notifyDataSetChanged();
notifyDataSetChanged - "Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself." It doesn't reload data from database. You still have to remove the item from the adapter by calling remove method and then call notifyDataSetChanged
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?
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.
I'm doing a ListView in a fragment using loaders, ActionBarSherlock and SqliteCursorLoader. The ListView basically shows a list of dates and GPS coordinates. I can load the inital ListView just fine. I want to be able to reload the ListView upon clicking of a button to go forward and back in time and show the relevant data.
My problem is that I must not be calling restartLoader() in the right place, Eclipse is giving me a compile error when I try and call it using "this" as the third parameter. It is saying that "this" is of type View, and it needs to be of type LoaderCallbacks. How do I get the third parameter? I tried to use all variations of getSherlockActivity().getSupportLoaderManager().getLoader(LOADER_ID) and tried to cast to LoaderCallbacks and no luck.
This is my first attempt using a Loader.
Here is my onActivityCreated:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
gps = new GPSTracker(getActivity());
mGPSDC = new GPSDateCoordinates();
Log.d(TAG, "onActivityCreated");
CalcDate();
//enables buttons for next and previous months
GetPreviousFollowingMonths();
adapter= new SimpleCursorAdapter(getActivity(), R.layout.row, null, new String[] {
myDbHelper.COLUMN_DISTANCE, myDbHelper.COLUMN_FORMATTEDSTART },
new int[] { R.id.title, R.id.value },0);
lvReports.setAdapter(adapter);
btnDate.setText(getMonth(BaseMonth) + " " + String.valueOf(BaseYear));
getSherlockActivity().getSupportLoaderManager().initLoader(LOADER_ID, null, this);
// show next month's data
btnNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
getSherlockActivity().getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
getSherlockActivity().getSupportLoaderManager().getLoader(LOADER_ID).forceLoad();
}
});
}
and here is the Loader code:
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
loader = new SQLiteCursorLoader(getActivity().getApplicationContext(), myDbHelper,
myDbHelper.getSQLArrayOfDatesDistances(BaseYear,BaseMonth),null);
return loader;
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
adapter.changeCursor(cursor);
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
adapter.changeCursor(null);
}
You'll want to use YourActivityClassName.this rather than this (which is the View.OnClickListener class instance) in order to refer to the Activity's instance.
this question is is similar to this
* Android - Listview delete item and Refresh
and this (the same , but I added the full code here to check if I have any problems in my code):
please give me code example. . .
can i call an intent to refresh my list ?
I cant refresh my adapter with :
adapter.notifyDataSetChanged();
I tried:
adapter.remove(adapter.getItem(pos));
but without success, just one time (weird...).
there is another answer there:
Call that Activity once again Using Intent
sombody can give me the exact code for this (or for the adapter/cursor) ?
I am trying this for a couple of hours without success.
my full code:
protected void onCreate (Bundle SavedInstanceState) {
super.onCreate(SavedInstanceState);
setContentView(R.layout.personalmessageview);
headtitle= getIntent().getExtras().getString("head");
setTitle(headtitle);
personalresults = getIntent().getExtras().getStringArrayList("personalres");
personalresultswithtime = getIntent().getExtras().getStringArrayList("personalrestime");
// setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,personalresults));
ListView list = (ListView)findViewById(R.id.listview_personal);
// ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, personalresults);
list.setAdapter(adapter);
registerForContextMenu(list);
list.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView<?> av, View v, int pos, long id) {
String time = personalresultswithtime.get(pos).toString();
Show_Alert_box(v.getContext(),"Please select action.",time,pos);
return true;
}
});
public void Show_Alert_box(Context context, String message,String time,int position)
final String timestamp = time;
final int pos = position;
final AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setTitle(getString(R.string.app_name));
alertDialog.setButton("Delete", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try
{
db = databaseHelper.getWritableDatabase();
db.delete("messages","timestamp" + "=?", new String[] { timestamp });
Log.d("DB"," delete! ");
ArrayAdapter<String> adapter = new ArrayAdapter<String>(PersonalMessageView.this, android.R.layout.simple_list_item_1, personalresults);
adapter.remove(adapter.getItem(pos)); //not working t all! why ?
list.notify();
list.invalidate();
personalresults.remove(pos);
personalresultswithtime.remove(pos);
adapter.notifyDataSetChanged();
db.close();
}
catch(Exception e)
{
}
} });
alertDialog.setButton2("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
} });
alertDialog.setMessage(message);
alertDialog.show();
}
Inside your onClick of Dialog, you are dealing with an entirely new Adapter.There is no accociation of adapter(inside onClick()) to the listView Either you should say list.setAdapter(adapter); inside the onClick() method or make the adapter global.
instead of using
adapter.remove(adapter.getItem(pos));
use
string str=list.getItemAtPosition(index).toString();
personalresults.remove(str);
adapter.notifyDataSetChanged();