Reputation: 13154
I have a working implementation of a ContentProvider
loading data via CursorLoader
into a listview (with custom CursorAdapter
). It's a list of events. Every item has a title, place, etc. but also a set of offers which should be displayed in a LinearLayout
inside every list row.
The problem is that a Cursor
row can only contain flat data, not a set of other items.
My only idea is to make a joined query on database like this:
SELECT * FROM events, offers WHERE events.id=offers.event_id;
But then I'll have as much rows as there are offers (and the list should display events, so it's not good) and the list would be overpopulated. Maybe there is a possibility to tell CursorAdapter
to only populate list rows with unique events.id
but somehow retrieve the offers data as well?
The best solution would be to put a Cursor
or custom Object
containing offers inside the events Cursor
. But afaik it's not possible.
Upvotes: 2
Views: 2131
Reputation: 3428
I was facing the same problem. In fact, I think a lot of people are. The whole mechanism of URI - to Relational DB through contentprovider, and everything that was built around it (like the various change listeners, file and stream handling) - this is all very impressive and useful, but for very simple data models.
Once your application needs a more elaborate data model, like - a hierarchy of tables, object relational semantics - this model breaks. I've found a bunch of ORM tools for Android, but they seem too 'bleeding edge' to me (plus, for the life of me, I couldn't figure out if they have data change notification support).
ORM is very common today, I really hope the Android folks agree and add ORM capabilities to the platform.
This is what I ended up doing: A cursor of cursors, with a a leading index cursor that helps choose the correct internal curosr. It's kind of a temp solution, I just needed to move on with my code and get back to this later. Hope this helps. Of course if you use a listview, you probably need to also create a custom adapter to inflate the correct views, and do the binding.
public class MultiCursor implements Cursor {
private final String TAG = this.getClass().getSimpleName();
ArrayList<Cursor> m_cursors = new ArrayList<Cursor>();
Map<Long, CursorRowPair> m_idToCursorRow = Collections.synchronizedMap(new HashMap<Long, CursorRowPair>());
Set<Long> m_idSet = new HashSet<Long>();
Cursor m_idCursor;
/**
* @precondition: _id column must exist on every type of cursor, and has to have index of 0 (be the first)
* @param idCursor
*/
public MultiCursor(Cursor idCursor) {
m_idCursor = idCursor;// this cursor binds the order (1,2,3) to ids
// go over all the ids in id cursor and add to m_idSet
initIdSet();
// m_cursors.add(idCursor);
// m_position = -1;
}
private void initIdSet() {
m_idSet.clear();
long id;
m_idCursor.moveToPosition(-1);
while (m_idCursor.moveToNext()) {
id = m_idCursor.getLong(m_idCursor.getColumnIndex(ContentDescriptor.ShowViewItem.Cols.ID));
m_idSet.add(id);
}
m_idCursor.moveToFirst();
}
public void addCursor(Cursor cursor) {
// when something changes in the child cursor, notify parent on change, to notify subscribers
// cursor.registerContentObserver(new SelfContentObserver(this)); // calls my onchange, which calls the ui
m_cursors.add(cursor);
updateIdToCursorMap(cursor);
}
private class CursorRowPair {
public final Cursor cursor;
public final int row;
public CursorRowPair(Cursor cursor, int row) {
this.cursor = cursor;
this.row = row;
}
}
private void updateIdToCursorMap(Cursor cursor) {
// get object_type
// for each row in cursor, take id, row number
// add id, <cursor,rowNum> to map
long id;
int row = 0;
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
id = cursor.getLong(cursor.getColumnIndex(ContentDescriptor.ShowViewItem.Cols.ID));
if (m_idSet.contains(id)) m_idToCursorRow.put(id, new CursorRowPair(cursor, row));
row++;
}
cursor.moveToFirst();
}
private Cursor getInternalCursor() {
if (getPosition() < 0 || getCount()==0) return m_idCursor; // todo throw a proper exception
// get the id of the current row
long id = m_idCursor.getLong(m_idCursor.getColumnIndex(ContentDescriptor.BaseCols.ID));
CursorRowPair cursorRowPair = m_idToCursorRow.get(id);
if (null == cursorRowPair) return null;
Cursor cursor = cursorRowPair.cursor;
int row = cursorRowPair.row;
cursor.moveToPosition(row);
return cursor;
}
// //////////////////////////////////////////////
@Override
public void close() {
Log.d(TAG, "close");
for (Cursor cursor : m_cursors) {
cursor.close();
}
m_idCursor.close();
}
@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
Log.d(TAG, "copyStringToBuffer");
getInternalCursor().copyStringToBuffer(columnIndex, buffer);
}
etc etc etc.
Upvotes: 3
Reputation: 13154
Unfortunately one CursorLoader
can only load one Cursor
. So the solution was to write a custom AsyncTaskLoader
which returned two Cursors
.
Upvotes: 0
Reputation: 16393
In you're adapter query the offers cursor for all records and make it a class variable. Then in your getView
use the event id to iterate through the offer cursor and add the necessary textviews to your row layout when it find an appropriate match. It's not elegant, but it should work.
Upvotes: 0