I have two SQLite tables:
Service: sid (prim.key), workDesc, title
ServiceInstance: ssid (prim.key), sid(foreign key), date, workComment, odometer
Today I'm using a CustomAdapter extending SimpleCursorAdapter to feed a ListView with data from these tables. But since this method is deprecated, I want refactor the code using LoaderManager/CursorLoader instead.
Now, my confusion is that in the ListView, I want to show the date from the ServiceInstance table and the title from the Service table. Like:
2013-06-05 Regular service
How can I do that? Since now when I'm using my extended SimpleCursorAdapter-class, I'm sending a cursor to the constructor. But in this case when I have changed the database structure a bit, I need to show data from two different cursors (Service and ServiceInstance).
I have googled and read a couple of tutorials but not found any similar case. But here, http://www.mysamplecode.com/2012/11/android-database-content-provider.html
Where they declare the string array columns (step 6, MainActivity.java:58), can I just add the column names (date and title) even though they exist in different tables?
Another question, I don't plan to provide these data to other apps, so is it a meaning to create a ContentProvider anyway?
it shouldn't be necessary to bind to two different cursors in your case (and i wouldn't recommend it, since you would have to write your own adapter in that case).
Try to query across your tables instead, check this for a starting point:
SQLite query from multiple tables using SQLiteDatabase
how to use join query in CursorLoader when its constructor does not support it
For your second question, if you don't want to expose your ContentProvider to other applications set:
<provider
....
android:exported="false">
</provider>
in your manifest
Related
I'm implementing a Content Provider, which is backed by a fairly complex SQLite DB schema. The database has a few junction tables and I'm unsure whether they should be visible to the user of the Content Provider or not.
The parent tables are exposed via the Contract, each one has its own content URI, etc. Now, when inserting data via ContentResolver#applyBatch() method, I create ContentProviderOperation per each table's content URI. So far everything is clear. But my problem is, how should the junction tables be populated, as they don't have their own content URIs?
To illustrate this, here's an example. I have 2 "parent" tables, Movies and Actors. The relationship between them is many-to-many and therefore I have a junction table called MoviesActors.
To insert at one batch I do the following:
List<ContentProviderOperation> operations = new ArrayList<>;
// movie
operations.add(ContentProviderOperation.newInsert(Contract.Movie.ContentUri).withValue("movie_id", "23asd2kwe0231123sa").build());
// actor
operations.add(ContentProviderOperation.newInsert(Contract.Actor.ContentUri).withValue("actor_id", "89asd02kjlwe081231a").build());
getContentResolver().applyBatch(authority, operations);
The junction table MoviesActors should be inserted with a row containing movie_id and actor_id. How do I take care of the junction table in this situation?
The only thing, which comes to my mind is extend the Contract to have content URI pointing to the junction tables and add another ContentProviderOperation, since otherwise, how do you communicate movie_id and actor_id to ContentProvider#applyBatch()?
I rather not expose the junction table to the user of the ContentProvider, but I might be wrong here... perhaps that's how it should be done on Android?
I've searched this topic for days already and haven't found an answer to that.
Any help would be greatly appreciated.
Bonus question:
Is it necessary to expose every single table via the Contract? For instance, when having child tables in one-to-many relationship. I'm specifically referring to Insert/Update/Delete since I know with Query I can simply do a join, but maybe I'm wrong also here.
Thanks a lot!
NOTE: I'm not interested in 3rd party library solutions.
I think you're tackling the problem from the wrong end. You're trying to design an interface to match your database structure, but the interface should come first.
In the first place, the interface should meet all the requirements of your ContentProvider client. If your ContentProvider client needs access to the junction table you'll have to expose it (in some way, see below), otherwise you don't have to. A good interface hides the actual implementation details, so the ContentProvider client doesn't need to care about whether the ContentProvider is backed by an SQLite database, by a bunch of in-memory maps or even a web-service.
Also, you should not think of a ContentProvider just as an interface to a database and the Contract as the database schema. A ContentProvider is much more versatile and powerful than that. The major difference is that ContentProviders are addressed by URIs whereas in SQL you just have table names. In contrast to a table name, a URI has a structure. URIs have a path that identifies the object (or directory of objects) that you want to operate on. Also you can add query parameters to a URI to modify the behavior of an operation. In this respect a ContentProvider can be designed much like a RESTful service.
See below for a concrete (but incomplete) example of a Contract of a simple movie database. This is basically how one would design a RESTful web-service, except for one thing: Just like in your code, movie-id and actor-id are provided by the caller. A real RESTful service would create and assign these automatically and return them to the caller. A ContentProvider can only return long IDs when inserting new objects.
Insert a new movie
insert on /movies/
Values: {"movie_id": <movie-id>, "title": <movie-title>, "year": ...}
Insert a new actor
insert on /actors/
Values: {"actor_id": <actor-id>, "name": <actor-name>, "gender": ...}
Add an existing actor to a movie
insert on /movies/movie-id/actors/
Values: {"actor_id": <actor-id>}
Add an existing movie to an actor:
insert on /actors/actor-id/movies/
Values: {"movie_id": <movie-id>}
Optional: add a new actor directly to a movie:
insert on /movies/movie-id/actors/
Values: {"actor_id": <actor-id>, "name": <actor-name>, "gender": ... }
If no actor with the given id exists, this operation will create the new actor and link it to the movie in a single step. If an actor with this ID already exists an exception would be thrown.
The same could be done the other way round, adding a new movie to an actor.
Delete an actor from a movie
delete on /movies/movie-id/actors/actor-id
or
delete on /actors/actors-id/movies/movie-id
Get all movies
query on /movies/
Get a specific movie
query on /movies/movie-id
Get all actors playing in a specific movie
query on /movies/movie-id/actors/
Get all movies a specific actor has played in
query on /actors/actor-id/movies/
The optional query selection statement can be used to filter the result. To get movies from the last 10 years a specific actor has played in, you would add the selection movies_year>=2005 to the last query.
By using a contract like this you wouldn't expose the junction table, instead you provide a REST-like interface to your database.
The job of the ContentProvider is to map these operations onto the database or any other back-end.
I am designing an Android app with a fairly complex (normalized) database structure. My question is, what is the best design pattern for working with my data?
The database has a table Books, a table Authors, a table Collections (of books), and a junction table Books_Collections that relates the two based on an integer ID. I say that it is fairly complex because I want to display a list of books & authors in a specific a collection in a ListView in format "Book Title (Author Name)", so I need to be able to perform the following query (I will abbreviate the table names A, B, C, and B_C):
SELECT B.title, A.name
FROM B
JOIN A ON B.author_id = A.id
JOIN B_C ON B_C.book_id = B.id
WHERE B_C.collection_id = [variable]
I have seen some people advising developers to wrap their Sqlite databases in a Content Provider to easily take advantage of the Loaders introduced in Honeycomb. But is this really feasible for such complex queries? (If so, how?) or would it be better to just use DAO since I am not sharing my data with other apps? And if using DAO, should a custom AsyncTaskLoader be created to connect the data to a ListView?
EDIT
One more important detail: I want to change the background color of the list items based on a boolean variable in Books marking whether or not the book has been read, and a context menu will allow users to delete books from the list, so the list needs to adjust instantly to data changes.
Yes, is doable and easily done
I'm no expert but im followin the pattern used by google on his Google I/O app
https://code.google.com/p/iosched/
Check the provider package where u have the 3 classes needed for using the pattern
ScheduleDatabase.java
(definition of sqlite3 database)
ScheduleProvider.java
(Content provider atttached to this database)
ScheduleContract.java
(Contract defined to expose the provider)
Personally, in the case of join
in the Database File i define
interface Tables {
String TABLE1= "table1";
String TABLE2= "table2";
// JOINS
String TABLE1_JOIN_TABLE2 = "table1"
+ "LEFT OUTER JOIN table2 ON table1.table_id= table2.tableid";
}
and then in your provider map a provider entity to this join.
I ended up using database helpers/adapters and creating a custom AsyncTaskLoader. I wasn't able to find a ton of examples of custom ones, and there's not great documentation on it, but it's not too hard to figure out. Works like a charm.
I have in my db 2 table with a many to many relationship.
TAB_ARTICLES: {_ID, TITLE, BODY, DATE}
TAB_TAG: {_ID, NAME, COLOR, DATE}
TAB_ART_TAG: {_ID, ARTICLE_ID, TAG_ID}
I need to populate a ListView, one row for article and in every row I need to have a TextView for every label linked to that article. Like the following image
I think 2 solutions.
a. I use a CursorAdapter with a cursor made only on TAB_ARTICLE and than in every row I do a query to join the other 2 tables looking for all tags related at this article. This solution require a lot of db accesses.
b. I realize a temporary table
TABLE_TEMP: {ARTICLE_TITLE, ARTICLE_BODY, ARTICLE_DATE, TAG1_NAME, TAG1_COLOR, TAG2_NAME, TAG2_COLOR, ...}
and I use a query on this table as cursor for custom adapter. This solution use more space and have a limitation on possible displayed tags due to table columns.
Are there other ways?
Well, actually, it's a multicriterion thing: time, space, updates, search, etc. So there's no single recipe. It's very probable, however, that multiple queries will bog down scrolling. Worse, on some devices only. A temporary table may or may not be OK depending on the overall size of your data. And you may want to keep this redundant table in sync with the main one, making simultaneous updates to both.
One of the simplest trade-offs could be adding a redundant TEXT/CLOB column with the tag data (XML, JSON, other markup/separated format) to TAB_ARTICLES and keeping it in sync with your detail data. By the way, you will really need the M:M schema only if your queries substantiate that. Otherwise, the single table would suffice.
Again, I'd list and evaluate all the criteria first and decide what dimensions really need to be scalable and simplify the rest.
I have a ContentProvider which handles all the data insertion and retrieval related to my application, I'm following the pattern suggested by Virgil Dobjanschi on Google I/O. I am using the first pattern.
My problem is that I have a logical entity that was represented by multiple tables in the database.
For example, I have an Articles table and an ArticleExtras table. Articles represents the article itself, while ArticleExtras represents addtional information about certain Article, like number of comments.
I used CursorAdapter in the UI to display the Article title and the number of comments of that Article in one row of ListView.
To implement that, I added a left outer join ArticleExtras on statement in my ContentProvider query method for Article table, in order for CursorAdapter to get ArticleExtras along with the Article itself.
When new Articles are fetched from the web, I insert it into the database via ContentProvider, and then the CursorAdapter got notified and update the UI, this part worked as expected.
But when I fetched the number of comments (ArticleExtras), I want the same CursorAdapter, which is watching for changes in the content://com.myapp.content/Articles, to be notified, too, so I can update my row in the ListView.
My current implementation is like this: After inserting ArticleExtras into the database, I start a new query to check if Articles table has any rows that is related to the ArticleExtras I just inserted. If so I'll make a new uri for that Article( for example: content://com.myapp.cotent/Articles/123), and call getContext().getContentResolver().notifyChange(uri, null), so the corresponding CursorAdapter that is watching for changes of this Article will get notified.
Is the approach correct, or is there any better way to implement what I want?
Checkout ContactsProvider2, in it they set the notification uri to the AUTHORITY_URI which appears to be a catch all for the other URIs in the provider. I had the same probem and I have tried this myself for a provider with multiple tables and joins on those tables, and it works fine.
I'm implementing a PRIVATE ContentProvider which has few tables with relationships (one to many, many to many). In my current implementation all of the tables are accessible by URIs.
how can I simplify the interface so the inner 'through' tables won't have to be accessed by URIs ?
for example, I have a POSTS table, each POST has many TAGS through the TAGGINGS table.
I want to interact only with the POSTS URI and do the 'private' work inside of the ContentProvider.
for query its simple to return a cursor with joined tables, but how do I do this with insert ? is bulkInsert what I should look into ?
It is a limitation of ContentProvider. If you are not exposing your data to other applications you can use your custom database adapter implementation with methods and queries straight hitting your requirements.
bulkInsert() won't help in this situation as it inserts rows only into one table at once. But take a look at ContentProvider.applyBatch() method and ContentProviderOperation, ContentProviderOperation.Builder classes (you may need withValueBackReference() for one-to-many inserting).
These links should help you understand how to use them:
http://www.grokkingandroid.com/better-performance-with-contentprovideroperation/
http://www.grokkingandroid.com/androids-contentprovideroperation-withbackreference-explained/
What are the semantics of withValueBackReference?
But notice, using ContentProviderOperation is much slower than bulkInsert() if you are inserting many rows at once, as it parses Uri (string comparisions) each time the operation is going to be performed. Doing this way you still have to expose Uri for inserting into child table.
If you decide to use applyBatch(), overwrite it in your provider so it performs all operations in one transaction, so you retain consistency in data and speed up database operations:
#Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
ContentProviderResult[] results = super.applyBatch(operations);
db.setTransactionSuccessful();
return results;
} finally {
db.endTransaction();
}
}
You are free to insert to multiply tables as long as the values needed are provided.
For example:
ContentValues v = new ContentValues();
v.put("title","post1");
v.put("tag","tag1");
getProvider().insert(POST_URI,v);
In the implementation of insert, you could check if fields (tag) belongs to other table exists.If it does , it means that you should do extra works - insert tag first if it does not exist, set up correct association between the tag and the post just inserted.
You can check the source code of android contacts for the reference.
UPDATE:
To insert multiply tags, one hack-y way is to insert a comma separated string. THis is not elegant but it works.
Just to get this right: You want to have one URI and insert a post and all its tags with one insert call to the ContentProvider? Correct?
The problem is, that you need to have all values in the ContentValues object. There is a reason for normalization in database. Nevertheless it might be doable. For tags this should be easy. Just use one String for all tags. For example "android, ios, bada, wp7" and parse this string in your insert method.
You could also use a naming plus integer convention. And as long as there is a tag1, tag2,... tagX you would read these values from within your ContentProvider's insert method.
Neither is elegant, but would work.
In this case bulkInsert or applyBatch have no place in your code. They come only into play, if you want to use multiple calls to your ContentProvider at once and within one transaction.
But I think the better solution would indeed be to actually use multiple operations as described by biegleux.
Since you are going to be inserting into multiple tables the normal SQLiteDatabase.insert helper functions will not work. But this is completely doable in a performant and nice way.
You need to look at this from the endpoint of the user who is going to be inserting into you ContentProvider, even if it is only yourself. So first define the names or keys for all of you fields. Since you won't be using SQLiteDatabase.insert you don't actually need to name them the same as the database fields. None of the names should be duplicate. If for example you have fields in two different tables overlap perhaps tag in TableA and in TableB you could define the name for those field as TableA.tag and TableB.tag. Or use semantic naming for more descriptive names that don't collide.
Next you need to create your insert queries using SQLiteStatement per this answer. Make sure the names you use in createInsert are the same ones that the callers of the ContentProvider use as keys in the ContentValues.