I'm using custom RecyclerView.Adapter to show the list items. I'm using LoaderManager.LoaderCallbacks<Cursor> to fetch the data from the database and in onLoadFinished() I use custom method to extract the data from the cursor and store it in the custom ArrayList one by one using while(cursor.moveToNext()). The list is loaded successfully. But upon screen rotation the list goes off.
When I debugged the code I got to know that the onLoadFinished() is called on screen rotation but the while loop is not working even if the cursor is not null and cursor count is greater than 0.
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
loadAndroidFlavours(cursor);
mCursor = cursor;
invalidateOptionsMenu();
}
private void loadAndroidFlavours(Cursor cursor) {
if (cursor.getCount() != 0) {
while (cursor.moveToNext()) {
int columnIndexID = cursor.getColumnIndex(AndroidFlavourEntry._ID);
int columnIndexFlavour = cursor.getColumnIndex(AndroidFlavourEntry.COLUMN_FLAVOUR);
int columnIndexVersion = cursor.getColumnIndex(AndroidFlavourEntry.COLUMN_VERSION);
int ID = cursor.getInt(columnIndexID);
String flavour = cursor.getString(columnIndexFlavour);
String version = cursor.getString(columnIndexVersion);
mFavoritesList.add(new AndroidFlavour(ID, flavour, version));
}
loadAndroidFlavours();
} else {
hideList();
}
}
I'm stuck. Please help with explanation. Regards!
Related
updated my code. My issue happens when i back out of the activity. Listview items are lost. I checked the Sqlite database and all items are saved, just not showing up again on listView when I reStart-Activity.
MainActivity
private ListView lst;
private CustomeAdapter cv;
private EditText nameEd, middleEd, lastEd;
private ArrayList<People> peopleArrayList;
private DataHelper myData;
peopleArrayList = new ArrayList<>();
OnCreate.....
public void addPerosn(View view) {
String myName = nameed.getText().toString();
String myMiddle = middleed.getText().toString();
String myLast = lasted.getText().toString();
boolean insert = myData.addData(myName, myMiddle, myLast);
if (insert == true) {
peopleArrayList.add(new People(myName, myMiddle, myLast));
cv = new CustomeAdapter(this, peopleArrayList);
lst.setAdapter(cv);
nameed.setText("");
middleed.setText("");
lasted.setText("");
} else {
Toast.makeText(getApplicationContext(), "Error", Toast.LENGTH_SHORT).show();
}
}
}
My DataHelper method i want to call to Show All
public Cursor showData(){
SQLiteDatabase db = this.getWritableDatabase();
Cursor data = db.rawQuery("SELECT * FROM " + TABLE_NAME, null);
return data;
}
Any suggestions are appreciated . Thanks
Make sure you have overridden getCount and it returns proper count.
#Override
public int getCount() {
return items.length;
}
Apart from above solution, I would recomment you to do it in proper way
a) Create a model/pojo class say Person which will have firstName,lastName and middleName
b) create a data set of Person, i.e list of person
c) create a method addPerson in adapter class, and call whenever you want to add new Person data into the list. addPerson method will also refresh the adapter by calling notifyDataSetChanged
d) In activity create adapter object only once, later on just use method of it say adapter.addPerson(person)
I've created some CursorWrapper class
public class DogsCursorWrapper extends CursorWrapper {
public DogsCursorWrapper(Cursor cursor) {
super(cursor);
}
public Dog getDog() {
Dog dog = new Dog();
dog.setDogId(getInt(getColumnIndex(DogTable.ID)));
dog.setDogName(getString(getColumnIndex(DogTable.NAME)));
dog.setDogKind(getString(getColumnIndex(DogTable.KIND)));
return dog;
}
}
Then i use queryDogs method to fill a cursor and then return an instance of wrapper class
private DogsCursorWrapper queryDogs() {
Cursor simpleCursor = db.query(
DogTable.TABLE_NAME, null,null,null,null,null,null);
DogsCursorWrapper dogsCursor = new DogsCursorWrapper(simpleCursor);
// simpleCursor.close(); // this line causes an error in runtime
return dogsCursor;
}
Next step i call the method above in method below:
private void loadDogs() {
DogsCursorWrapper dogsCursor;
dogs = new ArrayList<>();
try {
dogsCursor = queryDogs();
dogsCursor.moveToFirst();
while (!dogsCursor.isAfterLast()) {
dogs.add(dogsCursor.getDog());
dogsCursor.moveToNext();
}
} finally {
dogsCursor.close();
}
}
In fact i do close the dogsCursor in my last method and my question is: didn't i miss some cursor that i have to close? To be clear i have some doubts about simpleCursor in queryDogs method. Should i close that one?
Is it correct decision to use custom DogsCursorWrapper class in this way? Thanks a lot!
The line that has been commented out:
// simpleCursor.close(); // this line causes an error in runtime
is unnecessary. I think you're asking: Does it make a copy of the cursor? The answer is no. The cursor wrapper uses the cursor that is passed in.
The easiest way to verify that is to do:
private DogsCursorWrapper queryDogs() {
Cursor simpleCursor = db.query(
DogTable.TABLE_NAME, null,null,null,null,null,null);
DogsCursorWrapper dogsCursor = new DogsCursorWrapper(simpleCursor);
simpleCursor.close(); // this line causes an error in runtime
if (dogsCursor.isClosed()) { // Because of this
Log.w(TAG, "Houston we have a problem...");
}
return dogsCursor;
}
Running this code demonstrates that closing the original cursor also closes the cursor held by the CursorWrapper.
As the title says, when trying to paginate a ListView backed by a SimpleCursorAdapter and a CursorLoader, old cursors are getting closed, thus the below exception is being thrown. The first 2 pages load just fine (the first isn't using the MergeCursor and the second page is the first one to use the MergeCursor). I don't call any close() on any cursor whatsoever.
What is interesting is that while debugging, I cannot see the closed flags on any cursor being set as true, for what it's worth. Might be an issue with the MergeCursor then. Let me know if you guys have any solutions, I'm out of ideas.
Stack Trace:
android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method.
at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:139)
at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:74)
Code:
private List<Cursor> mCursorsList = new ArrayList<>();
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
{
if (!isLoading && !isAllLoaded &&
firstVisibleItem != 0 &&
firstVisibleItem == totalItemCount - visibleItemCount)
getActivity().getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, final Cursor data)
{
Cursor oldCursor = mCursorAdapter.getCursor();
mCursorsList.add(data);
if (oldCursor != null)
{
Cursor[] cursorArray = mCursorsList.toArray(new Cursor[mCursorsList.size()]);
MergeCursor newCursor = new MergeCursor(cursorArray);
mCursorAdapter.swapCursor(newCursor);
}
else // first cursor
{
mCursorAdapter.swapCursor(data);
}
}
#Override
public void onLoaderReset(Loader<Cursor> loader)
{
}
The main cause of this issue is that the CursorLoader manages the inner cursor, so whenever it needs to open a new one (such as a new page query), it closes the old cursor.
For a more simple pagination implementation, don't query with offsets, just bump the limit on every page, so that the new cursor contains all previous pages as well. Also, as Ian Lake suggested on Google+, sometimes you don't even need pagination, especially if you are doing complicated joins or sorting the data.
I have a weird behavior in my App I'm struggling to understand.
I have 2 lists (A & B), the initial view is list A with a textBox on top that filters the results of list B.
I use cursor loaders, adapters and a listview to implement these 2 lists.
When the user enters a keyword to search, I restart Loader B with the entered keyword and load it into the adapter and set the listview to display adapter B.
Everything runs fine BUT instead of displaying the results right away, a wrong behavior happens: The full List B is shown for a split second, then the filtered results shows.
Of course this creates a very bad UX (user experience), what I want to achieve here is the filtered results to be shown right away without the split second view of the full list B.
I really want to understand why this behavior is occurring... after all this implementation style of querying is the one used in the official example of the cursor loaders :)
The code is too big but these are the most relevant parts:
This is the onTextChange that produces the bad behaviour:
#Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
getLoaderManager().restartLoader(LOADER_ID_2, null, this);
}
These are the cursorLoader methods:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id== LOADER_ID_1)
{
//calculate and return cursorLoader
}
else
{
if (mFilter == null || mFilter.length() == 0)
{
return new CursorLoader(this, uri, CAdapter.PROJECTION, null,null,null);
}
//else
// do some calculations
return new CursorLoader(this,uri, CAdapter.PROJECTION, whereStmt, whereArgs, orderBy);
}
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (loader.getId()== LOADER_ID_2)
{
mCAdapter.swapCursor(cursor);
}
else if (loader.getId()== LOADER_ID_1)
{
MatrixCursor newCursor = new MatrixCursor(DAdapter.PROJECTION);
if (cursor.moveToFirst()) {
do {
// basically I'm grouping filtering the returned cursor and populating the new matrix cursor with the filtered data because I didn't find a better way to filter and group results (get distinct rows)
}while (cursor.moveToNext());
}
mDAdapter.changeCursor(newCursor);
//I used change cursor because the cursor loader will only handle closing the "cursor" and not the "newCursor" I created and put into the adapter, I hope this doesn't backfire :)
}
public void onLoaderReset(Loader<Cursor> loader) {
if (loader.getId()== LOADER_ID_2 )
mCAdapter.swapCursor(null);
else if (loader.getId()== LOADER_ID_1 )
mDAdapter.swapCursor(null);
}
Try this;
public Loader<Cursor> onCreateLoader(int id, Bundle args)
{
Loader<Cursor> cursorLoader
if (id== LOADER_ID_1)
{
//calculate and return cursorLoader
If(cursorLoader == null)
{
return new CursorLoader(this,uri,
CAdapter.PROJECTION, whereStmt, whereArgs, orderBy);
}//end inner if
}// end if
else if (mFilter == null || mFilter.length() == 0)
{
return new CursorLoader(this, uri, CAdapter.PROJECTION, null,null,null);
}// end else if
}// end method
Say I've got a Cursor just returned from a database call:
Cursor myCursor = db.rawQuery(someQuery, null);
According to the docs, the Cursor isn't actually populated before any calls are made to it, like getCount() for example. So my question is, does the following code actually query my database twice?
if(myCursor.getCount() > 1)
{
// Do something
}
else if(myCursor.getCount() == 1)
{
// Do something else
}
Or does Android cache the Cursor object after the first 'if' statement, making the 'else if' statement access the cached object instead?
No, it doesn't. The cursor object actually contains it's own count. You can see the source code for the SQLiteCursor object here.Take a look at the getCount() method:
#Override
public int getCount() {
if (mCount == NO_COUNT) {
fillWindow(0);
}
return mCount;
}
It will only check the first time. mCount is only updated when the Cursor is requeried (see QueryThread.run() on the same page).