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