I found a solution using sub-classes of ContentProvider.
Let's say you have table tblA and another table tblB. I recommend creating two classes "AContentProvider" and "BContentProvider". Most importantly, make sure that both tables are set up in the same database.
The main part of the solution is to override ContentProvider.query() in the ContentProvider that you will call from your CursorLoader - the URI decides which one it is:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(
"tblA LEFT JOIN tblB"
+ " ON ("
+ "tblA.b_id"
+ " = "
+ "tblB.id"
+ ")"
);
...
// Content of projection is set by CursorLoader
// usually in an Activity that implements LoaderManager.LoaderCallbacks<>
Cursor c = qb.query(
database,
projection,
selection,
selectionArgs,
groupBy,
having,
orderBy
);
...
return c;
}
As you can see, the JOIN is done in setTables(). Using projection you make sure that you only display the columns you really need and most of all, you have no duplicate columns, like "id" from both tables:
final String[] projection = new String[] {
"tblA.*",
"tblB.columnThatOnlyBHas"
};
Make use of the override and try to get as much work done in the sub-classes as you can; for example: all my overridden query() methods call setNotificationUri() to notify the ContentResolver if the cursor result set changes.:
c.setNotificationUri(getContext().getContentResolver(), uri);