I have a database helper class that gets all of the rows of a table from a SQLite database. In the onCreate of my Main Activity I am calling that method and populating a ListView with that data.
I have a separate Activity that is getting info from a network and inserting rows into the database. After that is done I am calling finish() and it returns to the Main Activity.
When it returns the inserted rows are not being displayed in the ListView. From debugging, it appears that they are not being returned from the database helper method.
If I close and relaunch the app, the rows show up. If I change the screen orientation, they show up.
Here is my database helper method:
public List<Object> getAllObjects() {
List<Object> objects = new ArrayList<Object>();
Cursor cursor = database.query(SQLiteHelper.TABLE_OBJECT,
allColumns, null, null, null, null, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
Object object = cursorToObject(cursor);
objects.add(object);
cursor.moveToNext();
}
cursor.close();
return objects;
}
Your main Activity is not newly created, you return to an old instance. In your case, put the update code in the onResume method of your main Activity, not in the onCreate method.
See the Activity lifecycle for further details on when code is executed.
Related
I have an activity ActitvityA that holds a listview populated by a CursorLoader. I want to switch to ActivityB and change some data and see those changes reflected in listview in ActivityA.
public class ActivityA implements LoaderManager.LoaderCallbacks<Cursor>
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
mCursorAdapter = new MyCursorAdapter(
this,
R.layout.my_list_item,
null,
0 );
}
.
.
.
/** Implementation of LoaderManager.LoaderCallbacks<Cursor> methods */
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle arg1) {
CursorLoader result;
switch ( loaderId ) {
case LOADER_ID:
/* Rename v _id is required for adapter to work */
/* Use of builtin ROWID http://www.sqlite.org/autoinc.html */
String[] projection = {
DBHelper.COLUMN_ID + " AS _id", //http://www.sqlite.org/autoinc.html
DBHelper.COLUMN_NAME // columns in select
}
result = new CursorLoader( ActivityA.this,
MyContentProvider.CONTENT_URI,
projection,
null,
new String[] {},
DBHelper.COLUMN_NAME + " ASC");
break;
default: throw new IllegalArgumentException("Loader id has an unexpectd value.");
}
return result;
}
/** Implementation of LoaderManager.LoaderCallbacks<Cursor> methods */
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
switch (loader.getId()) {
case LOADER_ID:
mCursorAdapter.swapCursor(cursor);
break;
default: throw new IllegalArgumentException("Loader has an unexpected id.");
}
}
.
.
.
}
From ActivityA I switch to ActivityB where I change the underlying data.
// insert record into table TABLE_NAME
ContentValues values = new ContentValues();
values.put(DBHelper.COLUMN_NAME, someValue);
context.getContentResolver().insert( MyContentProvider.CONTENT_URI, values);
The details of MyContentProvider:
public class MyContentProvider extends ContentProvider {
.
.
.
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriCode = sURIMatcher.match(uri);
SQLiteDatabase database = DBHelper.getInstance().getWritableDatabase();
long id = 0;
switch (uriType) {
case URI_CODE:
id = database.insertWithOnConflict(DBHelper.TABLE_FAVORITE, null, values,SQLiteDatabase.CONFLICT_REPLACE);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null); // I call the notifyChange with correct uri
return ContentUris.withAppendedId(uri, id);
}
#Override
public Cursor query(Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder) {
// Using SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
int uriCode = sURIMatcher.match(uri);
switch (uriCode) {
case URI_CODE:
// Set the table
queryBuilder.setTables(DBHelper.TABLE_NAME);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase database = DBHelper.getInstance().getWritableDatabase();
Cursor cursor = queryBuilder.query( database, projection, selection, selectionArgs, null, null, sortOrder);
// Make sure that potential listeners are getting notified
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
}
As far as my knowledge goes this should be sufficient. But it does not work. Upon returning to ActivityA the listview is unchanged.
I have followed things with debugger and this is what happens.
First visit ActivityA, the methods that are called in that order
MyContentProvider.query()
ActivityA.onLoadFinished()
The listview displays the correct values.
And now I switch to activityB and change the data
MyContentProvider.insert() // this one calls getContext().getContentResolver().notifyChange(uri, null);
MyContentProvider.query()
//As we can see the MyContentProvider.query is executed. I guess in response to notifyChange().
// What I found puzzling why now, when ActivityB is still active ?
Return to ActivityA
!!! ActivityA.onLoadFinished() is not called
I have read anything I could about this, took a close look at a lot of stackoverflow questions yet all those question/answers revolve around setNotificationUri() and notifyChangeCombo() which I implemented. Why does this not work across activities?
If for example force refresh in ActivityA.onResume() with
getContentResolver().notifyChange(MyContentProvider.CONTENT_URI, null, false);
then it refreshes the list view. But that would force refresh on every resume regardless if data was changed or not.
After a two long two days of scratching my head and altruistic engagement from pskink I painted myself a picture of what was wrong.
My ActivityA is in a reality a lot more complicated. It uses ViewPager with PagerAdapter with instantiates listviews.
At first I created those components in onCreate() method something like this:
#Override
public void onCreate(Bundle savedInstanceState)
{
...
super.onCreate(savedInstanceState);
// 1 .ViewPager
viewPager = (ViewPager) findViewById(R.id.viewPager);
...
viewPager.setAdapter( new MyPagerAdapter() );
viewPager.setOnPageChangeListener(this); */
...
// 2. Loader
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
...
// 3. CursorAdapter
myCursorAdapter = new MyCursorAdapter(
this,
R.layout.list_item_favorites_history,
null,
0);
}
Somewhere along the line I noticed that this is wrong order of creating. Why it didn't produce some error is because PagerAdapter.instantiateItem() is called aftter onCreate() finishes. I dont know why or how this caused the original problem. Maybe something did not wire correctly with listviews, adapters and content observers. I didn't dig into that.
I changed the order to:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
...
// 1. CursorAdapter
myCursorAdapter = new MyCursorAdapter(
this,
R.layout.list_item_favorites_history,
null,
0);
...
// 2. Loader
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
...
// 3 .ViewPager
viewPager = (ViewPager) findViewById(R.id.viewPager);
...
viewPager.setAdapter( new MyPagerAdapter() );
viewPager.setOnPageChangeListener(this); */
...
}
This magically made it work in about 75% of the cases. When I studied CatLog output I noticed that ActivityA().onStop() is called at different times. When it works it is called late and I can see in logcat that onLoadFinished() executes. Sometimes ActivityA.onStop() executes right after query and then onLoadFinished() is not called at all. This brings me to what DeeV jas posted in his answer about cursors being unregistered from ContentResolver. This just might be the case.
What made things to somehow came to light was the fact that simple demonstrator pskink insisted on did work and my app didn't although they were identical in key points. This brought my attention to asynchronous things and my onCreate() method. In reality my ActivityB is complicated so it gives enough time for ActivityA to stop.
What I noticed also (and this did make things more difficult to sort) was that if I run my 75% version in debug mode (with no breakpoints) then the success rate falls to 0. ActivityA is stopped before cursor load finishes so my onLoadFinished() is never called and listviews are never updated.
Two key points:
Somehow the order of creation od ViewPager, CursorAdapter and
CursorLoader is important
ActivityA may be (and is) stopped before
cursor is loaded.
But even this is not. If I take a look at a sequence of simplified then I see that ActivityA.onStop() is executed before content provider inserts a record. I see no query while ActivityB is active. But when i return to ActivityA a query is execeuted laodFinished() follows and listview is refreshed. Not so in my app. It always executes a query while still in ActivityB, why??? This destroy my theory about onStop() being the culprit.
(Big thanks to pskink and DeeV)
UPDATE
After a lot of waisted time on this issue I finally nailed the cause of the problem.
Short description:
I have the following classes:
ActivityA - contains a list view populated via cursor loader.
ActivityB - that changes data in database
ContentProvider - content provider used for data manipulation and also used by cursorloader.
The problem:
After data manipulation in ActivityB the changes are not shown in list view in ActivityA. List view is not refreshed.
After I lot of eyeballing and studying logcat traces I have seen that things proceed in following sequence:
ActivityA is started
ActivityA.onCreate()
-> getSupportLoaderManager().initLoader(LOADER_ID, null, this);
ContentProvider.query(uri) // query is executes as it should
ActivityA.onLoadFinished() // in this event handler we change cursor in list view adapter and listview is populated
ActivityA starts ActivityB
ActivityA.startActivity(intent)
ActivityB.onCreate()
-> ContentProvider.insert(uri) // data is changed in the onCreate() method. Retrieved over internet and written into DB.
-> getContext().getContentResolver().notifyChange(uri, null); // notify observers
ContentProvider.query(uri)
/* We can see that a query in content provider is executed.
This is WRONG in my case. The only cursor for this uri is cursor in cursor loader of ActivityA.
But ActivityA is not visible any more, so there is no need for it's observer to observe. */
ActivityA.onStop()
/* !!! Only now is this event executed. That means that ActivityA was stopped only now.
This also means (I guess) that all the loader/loading of ActivityA in progress were stopped.
We can also see that ActivityA.onLoadFinished() was not called, so the listview was never updated.
Note that ActivityA was not destroyed. What is causing Activity to be stopped so late I do not know.*/
ActivityB finishes and we return to ActivityA
ActivityA.onResume()
/* No ContentProvider.query() is executed because we have cursor has already consumed
notification while ActivityB was visible and ActivityA was not yet stopped.
Because there is no query() there is no onLoadFinished() execution and no data is updated in listview */
So the problem is not that ActivityA is stopped to soon but that it is stopped to late. The data is updated and notification
sent somewhere between creation of ActivityB and stopping of ActivityA.
The solution is to force loader in ActivityA to stop loading just before ActivityB is started.
ActivityA.getSupportLoaderManager().getLoader(LOADER_ID).stopLoading(); // <- THIS IS THE KEY
ActivityA.startActivity(intent)
This stops the loader and (I guess again) prevents cursor to consume notification while activity is in the above described limbo state.
The sequence of events now is:
ActivityA is started
ActivityA.onCreate()
-> getSupportLoaderManager().initLoader(LOADER_ID, null, this);
ContentProvider.query(uri) // query is executes as it should
ActivityA.onLoadFinished() // in this event handler we change cursor in list view adapter and listview is populated
ActivityA starts ActivityB
ActivityA.getSupportLoaderManager().getLoader(LOADER_ID).stopLoading();
ActivityA.startActivity(intent)
ActivityB.onCreate()
-> ContentProvider.insert(uri)
-> getContext().getContentResolver().notifyChange(uri, null); // notify observers
/* No ContentProvider.query(uri) is executed, because we have stopped the loader in ActivityA. */
ActivityA.onStop()
/* This event is still executed late. But we have stopped the loader so it didn't consume notification. */
ActivityB finishes and we return to ActivityA
ActivityA.onResume()
ContentProvider.query(uri) // query is executes as it should
ActivityA.onLoadFinished() // in this event handler we change cursor in list view adapter and listview is populated
/* The listview is now populated with up to date data */
This was the most elegant solution I could find. No need to restart loaders and such.
But still I would like to hear a comment on that subject from someone with a deeper insight.
I don't see anything here particularly wrong with this. As long as the Cursor is registered with the URI, the loader should be restarting itself with new information. I don't think the issue here is anything wrong with your code. I think it's the LoaderManager is unregistering the Cursor from the ContentResolver too early (it actually happens by the time onStop() is called).
Basically there's nothing you can really do about it unregistering. You can however, force restart the loader by calling LoaderManager#restartLoader(int, Bundle, LoaderCallbacks);. You can call this in onStart() (which makes the initLoader call in onCreate() useless). A more optimized approach would be to use onActivityResult(). The result of your activity is irrelevant in this case. All you're saying is that you've returned to this activity from some other activity and the data may or may not be different, so you need to reload.
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
}
Then just call Context#startActivityForResult() when opening new Activities.
I have a listview activity which populates data through an sqlite database; however, whenever I enter onPause and then go into onResume my app crashes and I receive this error: "java.lang.IllegalStateException: trying to requery an already closed cursor android.database.sqlite.SQLiteCursor#418106a8". Would anyone know how to stop this? Is there a method I have to call in onPause?
#Override
protected void onResume() {
super.onResume();
uGraduateListAdapter = new ArrayAdapter<String>(ListOfAlarms.this, android.R.layout.simple_list_item_1, populateList());
listOfAlarms.setAdapter(uGraduateListAdapter);
Log.i(TAG, "Resume was called");
}
#Override
protected void onPause() {
super.onPause();
Log.i(TAG, "Pause was called");
sqliteDatabase.close();
}
public List<String> populateList(){
// We have to return a List which contains only String values. Lets create a List first
List<String> uGraduateNamesList = new ArrayList<String>();
// First we need to make contact with the database we have created using the DbHelper class
AndroidOpenDbHelper openHelperClass = new AndroidOpenDbHelper(this);
// Then we need to get a readable database
sqliteDatabase = openHelperClass.getReadableDatabase();
// We need a a guy to read the database query. Cursor interface will do it for us
//(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
cursor = sqliteDatabase.query(AndroidOpenDbHelper.TABLE_NAME_ALARM, null, null, null, null, null, null);
// Above given query, read all the columns and fields of the table
startManagingCursor(cursor);
// Cursor object read all the fields. So we make sure to check it will not miss any by looping through a while loop
while (cursor.moveToNext()) {
// In one loop, cursor read one undergraduate all details
// Assume, we also need to see all the details of each and every undergraduate
// What we have to do is in each loop, read all the values, pass them to the POJO class
//and create a ArrayList of undergraduates
String alarmName = cursor.getString(cursor.getColumnIndex(AndroidOpenDbHelper.COLUMN_NAME_ALARM_NAME));
// String ugUniId = cursor.getString(cursor.getColumnIndex(AndroidOpenDbHelper.COLUMN_NAME_UNDERGRADUATE_UNI_ID));
String alarmTotalTime = cursor.getString(cursor.getColumnIndex(AndroidOpenDbHelper.COLLUMN_ALARM_TOTALTIME));
// Finish reading one raw, now we have to pass them to the POJO
TestAlarm ugPojoClass = new TestAlarm();
ugPojoClass.setTitle(alarmName);
ugPojoClass.setTotalTime(alarmTotalTime);
// Lets pass that POJO to our ArrayList which contains undergraduates as type
pojoArrayList.add(ugPojoClass);
// But we need a List of String to display in the ListView also.
//That is why we create "uGraduateNamesList"
uGraduateNamesList.add(alarmName);
}
// If you don't close the database, you will get an error
sqliteDatabase.close();
return uGraduateNamesList;
}
You are using deprecated methods (startManagingCursor()), which is dangerous.
How I see what happens: when you close your database (twice actually: in populateList() and onPause()), your cursors to this database become invalid. But since you called startManagingCursor(), your Activity retains your cursors and tries to call requery() on them when restarting, which throws the error.
Try not calling startManagingCursor() at all, just cursor.close() when you're done with it. Or you can migrate to newer LoaderManager altogether.
I have two activities: one NoteListActivity which inherits from ListActivity, and I used SimpleCursorAdapter as its adapter where the cursor is obtained as below:
public Cursor getAllNotesCursor() {
String selectQuery = "SELECT _id , title, content FROM " + NOTE_TABLE_NAME;
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
return cursor;
}
The another activity NoteEditorActivity is responsible for creating new note, there is a save action there and on click I will add a new note in the SQLite database then call finish to the NoteListActivity.
The problem is that the NoteListActivity didn't get updated with new note, do you know the best practice to achieve this?
One solution I can thought of is starting NoteEditorActivity by calling startActivityForResults then call cursor requery in onActivityResult, I don't know whether there is better solution?
startActivityForResults is good, but why not try to override onResume() method, with yourAdapter.notifyDataChange()
#Override
public void onResume() {
...
yourAdapter.notifyDataSetChanged();
}
Of course you have to add yourAdapter on your field class.
Whatever you are doing in onCreate method that is affecting UI to draw or show note by fetching from database.
Don't do it in onCreate.
DO IT IN onResume
#Override
public void onResume(){
//fetch here, do other operation, or set layout here
}
notifyDataSetChanged Update List View Adopter
http://androidadapternotifiydatasetchanged.blogspot.in/
try following steps..
use startActivityForResult() inside NoteListActivity to start NoteEditorActivity.
set RESULT_OK in save button click event
Populate list in onActivityResult() of NoteListActivity
I have an app that functions properly and does not force close or crash. But when I look at LogCat, it occasionally gives me this:
05-20 15:24:55.338: E/SQLiteDatabase(12707): close() was never explicitly called on database '/data/data/com.---.--/databases/debt.db'
05-20 15:24:55.338: E/SQLiteDatabase(12707): android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
a little ways down...
05-20 15:24:55.338: E/System(12707): Uncaught exception thrown by finalizer
05-20 15:24:55.338: E/System(12707): java.lang.IllegalStateException: Don't have database lock!
I am not sure when I should be opening and closing my Database?
I have a Main activity that is simply a splash screen. It then goes into an activity that calls a ListView using info from the DB; so it is at this activity where the DB is first opened.
There is also one other Activity where the DB is required that branches off the one with the ListVeew. When am I supposed to be opening and closing this? Word seems to be that I simply need to open once, and then close when the app is "paused", "stopped" or "destroyed".
If this is the case, where do I put the db.close() method... in the Splash Screen Main Activity where onStop, etc is located? or the same Activity as the one that opens the DB? or.. is there another place?
UPDATE:
This is the line in code that the error keeps pointing to:
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
If you're using an instance of a DatabaseHelper class, and after you initialize the DBHelper object, every time you do work in the database you should call the open method before you do work, then create a new cursor, query the database, do work with the information you just stored in the cursor, when you're done close the cursor, then close the database. For example if you wanted to grab every item in a database you would do something like :
...
DataBaseHelper db = new DataBaseHelper(this);
...
db.open();
Cursor cursor = db.getAllItems();
maxCount = cursor.getCount();
Random gen = new Random();
row = gen.nextInt(maxCount); // Generate random between 0 and max
if (cursor.moveToPosition(row)) {
String myString = cursor.getString(1); //here I want the second column
displayString(myString); //private method
}
cursor.close();
db.close();
getAllItems is a public method in my DatabaseHelper, it looks like this in case you were wondering
public Cursor getAllItems() {
return db.query(DATABASE_TABLE,
new String[] {
KEY_ROWID,
KEY_NAME
},
null,
null,
null,
null,
null);
}
This is how I access my database and I haven't gotten any of the errors you've got, and it works perfectly.
I used to do the way #Shikima mentioned above but in complex applications which has many background services, multi-threading,etc it can get real tiresome when you have to manage many database instances and on top of that, opening and closing them.
To overcome this, I used the following method and it seems to be working fine.
1.
Declare and initialize an instance of YourDBHelperClass in your Application base class like this :
public class App extends Application {
public static YourDBHelperClass db;
#Override
public void onCreate() {
super.onCreate();
db = new YourDBHelperClass(getApplicationContext());
db.open();
}
}
2.
In you activity, or any other place you want to use the DB, initialize the YourDBHelperClass object like this :
YourDBHelperClass db = App.db;
And then you can use the database anyway you want without having to worry about opening and closing it manually each time. The SQLiteOpenHelper takes care of the closing when the Application is destroyed
You are probably not handling your database correctly; you are opening more database instances than you are closing.
There are a number of design patterns you can follow to correct this behavior. You might want to consult this answer for more information.
I have an app that uses a cursor to select data via rawQuery from an SQLite DB to populate a ListView in Android. Every time the user clicks on a listview item I create a new instance of Activity to re-populate listview.
Is it better to call cursor.close() and db.close() to avoid memory problems? I actually have db.close() in OnDestroy() of my activity.
You can close the cursor once you have retrieved the values for that particular object inside your method.
btw...You don't have to recreate a listview every time for a user click event. Just notify that there is some change in data of your adapter that has been set on the listview.
Something like
youradaptername.notifyDataSetChanged();
This should repopulate contents inside ur listview automatically.
Well if you are creating a new instance every time of the same Activity (though I am not sure its a good programming practice). You can close the cursor as soon as your have finished traversing / iterating through the source of the listview.
Example:
A sample implementation would be something like
//Pre cursor code
startManagingCursor(cursor);
if (cursor.moveToFirst()) {
do {
if (cursor.getString(0).equals(value)) {
cursor.close();
a = true;
return a;
}
} while (cursor.moveToNext());
}
//Close cursor here, when its work is complete
cursor.close();
//Post cursor code ...