What to do in custom ContentProvider's fillWindow() method? - android

I'm writing a custom ContentProvider that serves up content consisting of a single, constant string which I represent as a one-row table having columns _id = 0 and value = "SomeString". This string is not stored in a database, so I developed a subclass of CrossProcessCursor that has does everything required to behave like what I described above.
The documentation for CrossProcessCursor is very sparse and doesn't really explain what the fillWindow() method should be doing beyond the obvious. Based on the descriptions of CursorWindow's methods, I put the following together, which I thought should cover it:
public class MyCursor implements CrossProcessCursor {
...
public void fillWindow(int pos, CursorWindow window) {
if (pos != 0) { // There's only one row.
return;
}
window.clear();
window.allocRow(); // TODO: Error check, false = no memory
window.setNumColumns(2);
window.setStartPosition(0);
window.putLong(0, 0, 0);
window.putString("SomeString", 0, 1);
}
}
As expected, it gets called with pos = 0 when a client application requests the content, but the client application throws an exception when it tries to go after the first (and only) row:
Caused by: java.lang.IllegalStateException: UNKNOWN type 48
at android.database.CursorWindow.getLong_native(Native Method)
at android.database.CursorWindow.getLong(CursorWindow.java:380)
at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:108)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:194)
at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:248)
at android.database.CursorWrapper.moveToFirst(CursorWrapper.java:86)
...(Snipped)...
Could anyone shed some light on what this method should be doing to return a correct-looking row to the client?
Thanks.

For what you're doing you should check out the MatrixCursor. It uses the AbstractCursor#fillWindow implementation which calls toString on every object. Since you're just sending a string anyway it should work fine for you.

Related

Pager with RecyclerViews containing realmObjects keeps crashing with IllegalState

I have a project using Realm.io for storing entities, this used to work fine, but now I have a fragment or activity containing 3 fragments with Lists of Realm objects.
Whenever I Switched to a page and back to the first one (or whatever just returning to a page). I get the java.lang.IllegalStateException: Illegal State: Row/Object is no longer valid to operate on. Was it deleted?
This seems to occur because the objects are no longer valid. Is there a simple way to detach them or something? Allthough it would be nice if they remain managed since I sometimes want to be able to delete them.
The items are queried from database, when there are not sufficient items they will get loaded from the API. Nothing extremely fancy is being used here, three lists with adapters which load the entities. THe difference per list is a string value status, which says if it's an certain status.
I get the error when I load the item from the Adapter after clicking the list item to show the details:
MyEntity myEntity = (MyEntity) adapter.getItem(position);
intent.putExtra("id", myEntity.getId()) <-- this part will crash it.
with exception:
java.lang.IllegalStateException: Illegal State: Row/Object is no longer valid to operate on. Was it deleted?
I guess it's because it's querying the same type of data on three locations (3 tabs). Though I would expect this not to be a problem since they all have their own adapter and list of items. Fetched from their own instances.
This is the code being called by my "Loader" class which handles the from DB and/or Api fetching.
public void loadResult(List result, boolean isFinished) {
//not the best for speed, but at a max of 10 items this is fine to not get duplicates and respect the original order
try {
for (RealmObject ro : result) {
Record r = (Record) ro;
int itemIndex = items.indexOf(r);
if (itemIndex > -1) {
items.set(itemIndex, r);
} else {
items.add(r);
}
}
} catch (IllegalStateException e) {
ErrorClass.log(e);
}
notifyDataSetChanged();
setLoading(!isFinished);
end = result.size() < 10 && isFinished;
}
in short the loader class does this, and it's not a singleton, it's a new instance per Listview (Recycler)
List result = null;
if (sortKey != null) {
result = query.findAllSorted(sortKey, ascending);
} else {
result = query.findAll();
}
if (result.size() < PAGE_SIZE && retry == 0) {
isFinished = false;
retry++;
getPageFromApi(pageNumber);
} else if (retry > 0) {
retry = 0;
}
adapter.loadResult(result, isFinished);
The getPageFromApi will result on this code being called again, and existing entities will be replaced in the list, new items added. So no old removed items should exist in the list when clicking them.
I think this might be very specific but there must be a global reason/solution to my problem.
Stupid me, I wrapped the adding of the new elements in a try catch because of the error before, what was going wrong is pretty simple. In the Loader the items fetched from our API was updating or creating new items. Meaning that those in the list, will be invalid at that point, or at least the pointers to them? Not sure how it works behind the scenes.
What I did to fix it, was loop through all the current items, and check the isValid(), if false the item would be removed. Otherwise I was checking for a new item to be inside the current items List, which would cause the error to occur in the .equals function!
This one thing is something that might be a core error, but I think it's just my error!

Android CursorLoader adding views dynamically in specific/certain order

This is probably very odd, but I'm using multiple CursorLoaders in Android to do multiple queries and in the onLoadFinished(), I am adding views like TextViews and ListViews to my layout dynamically based on cursor results like if the cursors were not null. I do get accurate results, but since I'm using AsyncTaskLoader (CursorLoader), the cursor results don't come in at the same time and the results are not added in the correct order. I previously used a static layout and added views at indices and did view.setVisiblity(View.GONE) based on the results, but it was just too much and too confusing because I have like 32 views. Plus it seemed weird because I don't think the user wants to see all of those views going away and moving up and down based on AsyncTaskLoader results.
How can I get the views in the correct order I want them in without having a bunch of boolean variables? I looked into LayoutInflater but that requires indices as well, but I'm not sure that will help me. The problem with indices for me is that in cursorloader ID 1:
view.addView(v, 1);
view.addView(v, 2);
might not get executed until the cursorloader with ID 2 finishes with:
view.addView(v, 3);
view.addView(v, 4);
If cursorLoader ID 1 doesn't get executed and ID 2 does, then there is missing space and I have to do a ton of view.setVisibility(View.GONE) if I use static XML views and do not dynamically add them.
In the code I'm doing something like this currently:
#Override
public void onLoadFinished(android.support.v4.content.Loader<Cursor> cursorLoader, Cursor cursor) {
switch (cursorLoader.getId())
{
case 0:
if (cursor != null && cursor.moveToFirst()) {
..
title = new TextView(this);
...
mainLinearLayout.addView(title, 1);
}
break;
case 1:
if (cursor != null && cursor.moveToFirst()) {
..
title2 = new TextView(this);
mainLinearLayout.addView(title2, 2);
break;
default:
...
}
}
I also read somewhere online that it is better to use a service instead of cursorloader if you want to do queries on the background thread and have them finish in a certain order, but I have not heard that advice anywhere else or seen any examples doing queries in services. They all use CursorLoader. Is this advice necessarily true? Sounds a bit sketchy.
By the way, I am using the CursorLoader implementation without a content provider given at CursorLoader usage without ContentProvider
How can I get the views in the correct order I want them in without
having a bunch of boolean variables?
You do need some sort of status control in order to make the views appear in order. I would delegate the view construction/addition to a control class that will have all the information required to make the correct view and in the right order no matter how the loaders finished their jobs.
public class ViewDispatcher {
public SparseArray<Status> mLoadStatuses = new SparseArray<Status>();
public SparseArray<Cursor> mDataCursors = new SparseArray<Cursor>();
// you'll call this method for each of the loaders, in the order they should be. The ids should be incremental
public void registerLoader(int loaderId) {
mLoadStatuses.put(loaderId, Status.INITIAL);
}
// called when one of the loaders finishes its job
public void onLoadComplete(int loaderId, Cursor data) {
mDataCursors.put(loaderId, data);
boolean current = true;
mLoadStatuses.put(loaderId, Status.LOADED);
if (loaderId == firstLoaderId) {
// the first loader it's done and we should start the view creation right away
buildView(loaderId, mainLayout, true);
mLoadStatuses.put(loaderId, data, Status.FULLY_BUILT);
} else {
// implement a priority system, a view construction will be triggered ONLY
// if the previous loader has finished loading data and its view is in place
// I'm assuming that the Loaders have consecutive ids
if (mLoadStatuses.get(loaderId - 1) != null && mLoadStatuses.get(loaderId - 1) == Status.FULLY_BUILT) {
buildView(loaderId, data, mainLayout, true);
mLoadStatuses.put(loaderId, Status.FULLY_BUILT);
} else {
current = false;
}
}
// we'll also need to implement a buddy system. When a loader is done loading and its view
// is created we must check to see if we don't have other loaders after this current one that have finished loading
// but couldn't construct their view because this current loader didn't finished at that moment
// get the next loader
int next = loaderId + 1;
while(current && next < totalNumberOfLoaders && mLoadStatuses.get(next) == Status.LOADED) {
// continue to build views
buildView(next, mDataCursors.get(loaderId), mainLayout, true);
mLoadStatuses.put(next, Status.FULLY_BUILT);
next++;
}
}
// this will build the appropriate view, and optionally attach it
public void buildView(int loaderId, Cursor data, view mainLayout, boolean attach) {
// build the view for this specific Loader
}
}
public enum Status {
INITIAL, LOADED, FULLY_BUILT
}
I hope I'm not missing something obvious as I wrote that without any tests. To use it, you'll first call the registerLoader() method for all loaders in the order you need them to be and in the onLoadComplete() callback of the LoaderCallbacks call ViewDispatcher.onLoadComplete().
I also read somewhere online that it is better to use a service
instead of cursorloader if you want to do queries on the background
thread and have them finish in a certain order, but I have not heard
that advice anywhere else or seen any examples doing queries in
services.
You've probably read about IntentService which can be made to follow a queue through the order of the Intents it receives. But, I don't see how this would help you as it would just add problems. For one you use Cursors as the data holders that you would need to pass back and you need to create views which the IntentService can't do(it will need to make the Activity create them through various communication ways, this is unnecessary work from my point of view).

Is Android Cursor.moveToNext() Documentation Correct?

boolean android.database.Cursor.moveToNext() documentation says:
http://developer.android.com/reference/android/database/Cursor.html#moveToNext%28%29
Move the cursor to the next row.
This method will return false if the cursor is already past the last entry in the result set.
However, my book says to do the following to extract data from a cursor:
Cursor myCursor = myDatabase.query(...);
if (myCursor.moveToFirst()) {
do {
int value = myCursor.getInt(VALUE_COL);
// use value
} while (myCursor.moveToNext());
}
Who's right? These both can't be true. If you can't see the contradiction, imagine myCursor has 1 row returned from the query. The first call to getInt() will work, but then moveToNext() will return true because it is not "already" past the last entry in the result set. So now the cursor will be past the last entry and the second call to getInt() will do something undefined.
I suspect the documentation is wrong and should instead read:
This method will return false if the cursor is "already at" the last entry in the result set.
Must the cursor be already PAST (not AT) the last entry before the moveToNext() method returns false?
No Snark Please
A simpler idiom is:
Cursor cursor = db.query(...);
while (cursor.moveToNext()) {
// use cursor
}
This works because the initial cursor position is -1, see the Cursor.getPosition() docs.
You can also find usages of cursors in the Android source code itself with this Google Code Search query. Cursor semantics are the same in SQLite database and content providers.
References: this question.
Verbatim from the API:
Returns:
whether the move succeeded.
So, it means that:
Cursor in first row -> moveToNext() -> cursor in second row -> there's no second row -> return false
If you want the details, go to the source: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/database/AbstractCursor.java#AbstractCursor.moveToNext%28%29
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
I think I tend to stay away from solutions that are based on a hidden assumption like: I hope that sqlite never changes it api and that a cursor will always start at the first item.
Also, I have almost always been able to replace a while statement with a for statement. So my solution, shows what I expect the cursor to start at, and avoids using a while statement:
for( boolean haveRow = c.moveToFirst(); haveRow; haveRow = c.moveToNext() ) {
...
}
why is showing that a cursor needs to start at the first row, well 6 months down the line you might be debugging your own code, and will wonder why you didn't make that explicit so you could easily debug it.
It appears to be down to the Android implementation of AbstractCursor and it remains broken in Jellybean.
I implemented the following unit test to demonstrate the problem to myself using a MatrixCursor:
#Test
public void testCursor() {
MatrixCursor cursor = new MatrixCursor(new String[] { "id" });
for (String s : new String[] { "1", "2", "3" }) {
cursor.addRow(new String[] { s });
}
cursor.moveToPosition(0);
assertThat(cursor.moveToPrevious(), is(true));
cursor.moveToPosition(cursor.getCount()-1);
assertThat(cursor.moveToNext(), is(true));
assertThat(cursor.moveToPosition(c.getCount()), is(true));
assertThat(cursor.moveToPosition(-1), is(true));
}
All assertions fail, contrary to the documentation for moveToNext, moveToPrevious and moveToPosition.
Reading the code at API 16 for AbstractCursor.moveToPosition(int position) it appears to be intentional behaviour, ie the methods explicitly return false in these cases, contrary to the documentation.
As a side note, since the Android code sat on existing devices in the wild cannot be changed, I have taken the approach of writing my code to match the behaviour of the existing Android implementation, not the documentation. ie. When implementing my own Cursors / CursorWrappers, I override the methods and write my own javadoc describing the departure from the existing documentation. This way, my Cursors / CursorWrappers remain interchangeable with existing Android cursors without breaking run-time behaviour.
Cursor.moveToNext() returning a boolean is only useful if it will not move the cursor past the last entry in the data set. Thus, I have submitted a bug report on the documentation's issue tracker.
https://issuetracker.google.com/issues/69259484
It reccomends the following sentence:
"This method will return false if the current (at time of execution) entry is the last entry in the set, and there is no next entry to be had."

Android Contentprovider - update within an insert method

Is it ok to call the SQLiteDatabase update method in the insert() overridden method of a content provider?
Basically it's fine, but since you didn't provided code, I just can post 2 possible ways for it
First:
// In your content provider
public Uri insert(...) {
long insertId = db.insert(...);
if(insertId == -1) {
// insert failed, do update
db.update(...);
}
}
Second:
public Uri insert(...) {
long insertId = db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE)
if(insertId == -1) {
// insert and update/replace failed
}
}
Check out SQLiteDatabase for reference on the forth parameter. In most cases the last method should be sufficient, unless you want only certain fields being updated if the row exists and have all fields if it doesn't.
Most useful need for insertWithOnConflict may be, that you could insert a row and if it already exists, ignore the insert and instead return the Uri/primary key of the already existing row.
It's your choice what you write in your overridden methods.
So yes, it is ok.
I don't know what you're trying to do, but you might want to to take a look on the SQLiteDatabase's replace() method too. Maybe it better suits your needs.

POJO's versus Cursors in Android

I usually tend to define the model layer of my apps using POJO's, such as Article, Comment, etc.
I was about to implement an AlphabetIndexer in the adapter of one of my ListViews. Right now this adapter accepts a Collection of Articles, which I normally get from my wrapper around an SQLiteDatabase.
The signature of the AlphabetIndexer constructer is as follows:
public AlphabetIndexer (Cursor cursor, int sortedColumnIndex, CharSequence alphabet)
Since this doesn't accept a Collection or something similar, just a Cursor, it got me wondering: maybe I shouldn't be creating objects for my model, and just use the Cursors returned from the database?
So the question is, I guess: what should I do, represent data with Collections of POJO's, or just work with Cursors throughout my app?
Any input?
I have run into similar issues. Right now, I am tending away from POJOs. Note, though, that you can create your own Cursor interface for a collection of POJOs, if you so choose.
I like to create Cursor-backed POJO classes. A Cursor-backed POJO class has a constructor that takes a Cursor and provides the following benefits:
Easy to use getters that return the proper content type, much better
than getting indexes and having to remember the type of data in the
database
Getter methods that compute their results from other getters, just like how OO programming ought to be
Getter return values can be enums!
These few benefits are well worth some boilerplate code, many many bugs have been averted now that user-engineers aren't accessing cursor columns themselves. We still use the CursorAdapter class, but the first line in the bindView method is to create the Cursor-backed POJO from the Cursor and from then on the code is beautiful.
Below is an example implementation, it's a snap for user-engineers to turn an opaque cursor into clearly defined User object, from that point on it can be passed around and accessed just like a regular POJO so long as the backing cursor is not closed. The SmartUserCursor is a special class I wrote to ensure that the cursor position is remembered and restored before the cursor is accessed and it also stores the cursor column indexes so lookups are fast.
EXAMPLE:
public class User {
private final SmartUserCursor mCursor;
public User(SmartUserCursor cursor, int position) {
mCursor = new SmartUserCursor(cursor, position);
}
public long getUserId() {
return mCursor.getLong(SmartUserCursor.Columns.userId);
}
public UserType getType() {
return UserType.valueOf(mCursor.getString(SmartUserCursor.Columns.type));
}
public String getFirstName() {
return mCursor.getString(SmartUserCursor.Columns.firstName);
}
public String getLastName() {
return mCursor.getString(SmartUserCursor.Columns.lastName);
}
public final String getFullName() {
return getFirstName() + " " + getLastName();
}
public static User newUserFromAdapter(BaseAdapter adapter, int position) {
return new User((SmartUserCursor)adapter.getItem(position), position);
}
public static User newUserBlocking(Context context, long UserId) {
Cursor cursor = context.getContentResolver().query(
Users.CONTENT_URI_CLIENT,
Users.DEFAULT_USER_PROJECTION,
Users.Columns.USER_ID+"=?",
new String[] {String.valueOf(UserId)},
null
);
if (cursor == null || !cursor.moveToFirst()) {
throw new RuntimeException("No User with id " + UserId + " exists");
}
return new User(new SmartUserCursor(cursor, Users.DEFAULT_USER_PROJECTION), -1);
}
public final void closeBackingCursor() {
mCursor.close();
}
}
One vote for entity objects (POJOs). Passing cursors around, especially to the UI layer, feels so wrong to me (whether or not the Android sdk kinda implies doing it that way). There are usually several ways to populate your UI, and I tend to avoid those that directly use cursors. For example, to populate my custom list views, I use a SimpleAdapter and give my collection objects the ability to return a representation of themselves as a List<? extends Map<String, ?>> for the SimpleAdapter's constructor.
I use a pattern where each table is wrapped by an entity object and has a provider class that handles my CRUD operations associated with that entity. Optionally if I need extended functionality for the collections, I wrap them too (ie. EntityItems extends ArrayList<EntityItem>) The provider has a base class that I pass a reference to a DbAdapter class that does the heavy lifting around the db.
The biggest reason, other than personal preference, is that I want to hide this kind of code as far away from my UI as possible:
String something = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_CONSTANT));
If I see that kind of code inline in the UI layer, I usually expect to see much worse lurking around the corner. Maybe I've just spent too much time in the corporate world working on big teams, but I favor readability unless there's a legit performance concern or if it's a small enough task where the expressiveness is just enterprisey overkill.
Answers are 4 years old.
I think now we have enought CPU power to get away with more stuff. My idea would be to work only with POJOs and ArrayLists; and extending CursorLoader to map cursor to POJOs in the background and deliver arraylist to activity;
unless youre queries hundreds of rows but then, how often are you doing that vs. niceness of using POJOs, getters and setters

Categories

Resources