Best practices for joining tables and notifying ContentObservers in Android ContentProvider - android

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.

Related

Android Content Provider SQLite Junction Tables

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.

How to know the moment when Data in database changed (ActiveAndroid ORM)?

I have a HTTP Server and Android app.
All data -> JSON format. For mapping used Gson.
ORM - ActiveAndroid.
The problem : I need something like an observer/notifier object, which can told me, that a row in database updated just now.
Something like this :
public void interface Observable<T> {
void onItemUpdated(T item);
}
So I looking a solution. I've read ActiveAndroid docs, but it doesn't get to me any result. Maybe i can mix something with ContentObserver or something like this?
How about to create a logging table where you can append a row for each update.
It has the insertion cost but select query is so fast. And also you can store update history of each record if you want or delete the log record if you are worry about performance.
you can create new field in your db which holds the updated date and time and after every operations in db you may query the db about the recent updates done to your db
You can accomplish this by implementing your own ContentObserver for the URI of the table you want to be notified about. ActiveAndroid has its own content provider you can use or you can implement your own, that works with your tables. In one of my projects I used ActiveAndroid and had my own content provider for 3rd party apps. Upon an update/insert/deletion of a row you should be notified by content provider that something was changed with reference to some URI and if you have a registered content observer to this URI you will be notified.

Implementing an Efficient ContentProvider on Android

I'm currently working on a SQLite code base with a table that can hold a large amount of records. It has so many records that using a LoaderManager to asynchronously retrieve Cursor objects is becoming slow to display them in a ListView with a CursorAdapter.
If there is one row change in the table being queried, the LoaderManager is notified, and a new Cursor is retrieved. But, this seems inefficient because the Cursor queries for all the rows in the table for the ListView. The GUI isn't being blocked because the Cursor loading is being done in another thread, the problem is that the retrieval of the table rows can take a while. 5-10 seconds can pass on some slower phones before the new record information is displayed.
I'm trying to find a way to efficiently retrieve row changes to update the ListViews's rows without reloading everything.
I've looked into rewriting my code as a internal ContentProvider (hiding SQLiteDatabase) because I've seen it can be used with the app's ContentResolver to send out individual row change notifications via notifyChange().
If I switch to a ContentProvider, will it be as efficient as I've assumed? Upon individual record changes, can the ContentProvider send out events that will allow a ListView to reload only the row change information, and not require a complete requery of all the table information?
when implementing query() method of your ContentProvider return a custom. AbstractWindowedCursor, that way even if the final data set is huge you just fill the small window

LoaderManager/CursorLoader for several cursors?

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

LoaderManager.onLoadFinished is called even when the specific query doesn't match the update

I have a Android app which has a Content Provider which I update frequently. To load the lists of the said data, I'm using a LoaderManager.
On the detail page, I also use a LoaderManager, but I create the CursorLoader like this:
return new CursorLoader(getActivity(), DataProvider.POST_URI, projection, "_id = ?", new String[]{String.valueOf(post_id)}, null);
As you can see, I'm only getting a single item from the table. When the row is updated the the onLoadFinished callback is updated, but, also, when other rows are added/modified to the table. It wouldn't be much of a hassle, but sometimes, it causes the whole list to get blank and redrawn, which can be frustrating for the user when it happens multiple times.
Do you know how could I avoid this behavior?
The reloading is taking place because you are associating this cursor loader with the DataProvider.POST_URI which I assume is the one you use for your posts table. Whenever there is a change in any of the elements in this table, all the cursors with that Uri will be notified. If you want to receive notification of a single item you should implement a new Uri scheme to match just that one item. Have a look at the documentation for the UriMatcher class for an example. You should do something similar to the PEOPLE and PEOPLE_ID cases.
EDIT
You can find a good tutorial on ContentProvider here. Section 9.4 in special should be helpful in correctly implementing the ContentProvider.

Categories

Resources