I found best demo and explanation for ContentProvider and I think it has followed Android Standards.
Contract Classes
/**
* The Content Authority is a name for the entire content provider, similar to the relationship
* between a domain name and its website. A convenient string to use for content authority is
* the package name for the app, since it is guaranteed to be unique on the device.
*/
public static final String CONTENT_AUTHORITY = "com.androidessence.moviedatabase";
/**
* The content authority is used to create the base of all URIs which apps will use to
* contact this content provider.
*/
private static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
/**
* A list of possible paths that will be appended to the base URI for each of the different
* tables.
*/
public static final String PATH_MOVIE = "movie";
public static final String PATH_GENRE = "genre";
and Inner Classes:
/**
* Create one class for each table that handles all information regarding the table schema and
* the URIs related to it.
*/
public static final class MovieEntry implements BaseColumns {
// Content URI represents the base location for the table
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_MOVIE).build();
// These are special type prefixes that specify if a URI returns a list or a specific item
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/" + CONTENT_URI + "/" + PATH_MOVIE;
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/" + CONTENT_URI + "/" + PATH_MOVIE;
// Define the table schema
public static final String TABLE_NAME = "movieTable";
public static final String COLUMN_NAME = "movieName";
public static final String COLUMN_RELEASE_DATE = "movieReleaseDate";
public static final String COLUMN_GENRE = "movieGenre";
// Define a function to build a URI to find a specific movie by it's identifier
public static Uri buildMovieUri(long id){
return ContentUris.withAppendedId(CONTENT_URI, id);
}
}
public static final class GenreEntry implements BaseColumns{
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_GENRE).build();
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/" + CONTENT_URI + "/" + PATH_GENRE;
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/" + CONTENT_URI + "/" + PATH_GENRE;
public static final String TABLE_NAME = "genreTable";
public static final String COLUMN_NAME = "genreName";
public static Uri buildGenreUri(long id){
return ContentUris.withAppendedId(CONTENT_URI, id);
}
}
Now creating Database using SQLiteOpenHelper:
public class MovieDBHelper extends SQLiteOpenHelper{
/**
* Defines the database version. This variable must be incremented in order for onUpdate to
* be called when necessary.
*/
private static final int DATABASE_VERSION = 1;
/**
* The name of the database on the device.
*/
private static final String DATABASE_NAME = "movieList.db";
/**
* Default constructor.
* @param context The application context using this database.
*/
public MovieDBHelper(Context context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* Called when the database is first created.
* @param db The database being created, which all SQL statements will be executed on.
*/
@Override
public void onCreate(SQLiteDatabase db) {
addGenreTable(db);
addMovieTable(db);
}
/**
* Called whenever DATABASE_VERSION is incremented. This is used whenever schema changes need
* to be made or new tables are added.
* @param db The database being updated.
* @param oldVersion The previous version of the database. Used to determine whether or not
* certain updates should be run.
* @param newVersion The new version of the database.
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
/**
* Inserts the genre table into the database.
* @param db The SQLiteDatabase the table is being inserted into.
*/
private void addGenreTable(SQLiteDatabase db){
db.execSQL(
"CREATE TABLE " + MovieContract.GenreEntry.TABLE_NAME + " (" +
MovieContract.GenreEntry._ID + " INTEGER PRIMARY KEY, " +
MovieContract.GenreEntry.COLUMN_NAME + " TEXT UNIQUE NOT NULL);"
);
}
/**
* Inserts the movie table into the database.
* @param db The SQLiteDatabase the table is being inserted into.
*/
private void addMovieTable(SQLiteDatabase db){
db.execSQL(
"CREATE TABLE " + MovieContract.MovieEntry.TABLE_NAME + " (" +
MovieContract.MovieEntry._ID + " INTEGER PRIMARY KEY, " +
MovieContract.MovieEntry.COLUMN_NAME + " TEXT NOT NULL, " +
MovieContract.MovieEntry.COLUMN_RELEASE_DATE + " TEXT NOT NULL, " +
MovieContract.MovieEntry.COLUMN_GENRE + " INTEGER NOT NULL, " +
"FOREIGN KEY (" + MovieContract.MovieEntry.COLUMN_GENRE + ") " +
"REFERENCES " + MovieContract.GenreEntry.TABLE_NAME + " (" + MovieContract.GenreEntry._ID + "));"
);
}
}
Content Provider:
public class MovieProvider extends ContentProvider {
// Use an int for each URI we will run, this represents the different queries
private static final int GENRE = 100;
private static final int GENRE_ID = 101;
private static final int MOVIE = 200;
private static final int MOVIE_ID = 201;
private static final UriMatcher sUriMatcher = buildUriMatcher();
private MovieDBHelper mOpenHelper;
@Override
public boolean onCreate() {
mOpenHelper = new MovieDBHelper(getContext());
return true;
}
/**
* Builds a UriMatcher that is used to determine witch database request is being made.
*/
public static UriMatcher buildUriMatcher(){
String content = MovieContract.CONTENT_AUTHORITY;
// All paths to the UriMatcher have a corresponding code to return
// when a match is found (the ints above).
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(content, MovieContract.PATH_GENRE, GENRE);
matcher.addURI(content, MovieContract.PATH_GENRE + "/#", GENRE_ID);
matcher.addURI(content, MovieContract.PATH_MOVIE, MOVIE);
matcher.addURI(content, MovieContract.PATH_MOVIE + "/#", MOVIE_ID);
return matcher;
}
@Override
public String getType(Uri uri) {
switch(sUriMatcher.match(uri)){
case GENRE:
return MovieContract.GenreEntry.CONTENT_TYPE;
case GENRE_ID:
return MovieContract.GenreEntry.CONTENT_ITEM_TYPE;
case MOVIE:
return MovieContract.MovieEntry.CONTENT_TYPE;
case MOVIE_ID:
return MovieContract.MovieEntry.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor retCursor;
switch(sUriMatcher.match(uri)){
case GENRE:
retCursor = db.query(
MovieContract.GenreEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
case GENRE_ID:
long _id = ContentUris.parseId(uri);
retCursor = db.query(
MovieContract.GenreEntry.TABLE_NAME,
projection,
MovieContract.GenreEntry._ID + " = ?",
new String[]{String.valueOf(_id)},
null,
null,
sortOrder
);
break;
case MOVIE:
retCursor = db.query(
MovieContract.MovieEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
case MOVIE_ID:
_id = ContentUris.parseId(uri);
retCursor = db.query(
MovieContract.MovieEntry.TABLE_NAME,
projection,
MovieContract.MovieEntry._ID + " = ?",
new String[]{String.valueOf(_id)},
null,
null,
sortOrder
);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// Set the notification URI for the cursor to the one passed into the function. This
// causes the cursor to register a content observer to watch for changes that happen to
// this URI and any of it's descendants. By descendants, we mean any URI that begins
// with this path.
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
return retCursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long _id;
Uri returnUri;
switch(sUriMatcher.match(uri)){
case GENRE:
_id = db.insert(MovieContract.GenreEntry.TABLE_NAME, null, values);
if(_id > 0){
returnUri = MovieContract.GenreEntry.buildGenreUri(_id);
} else{
throw new UnsupportedOperationException("Unable to insert rows into: " + uri);
}
break;
case MOVIE:
_id = db.insert(MovieContract.MovieEntry.TABLE_NAME, null, values);
if(_id > 0){
returnUri = MovieContract.MovieEntry.buildMovieUri(_id);
} else{
throw new UnsupportedOperationException("Unable to insert rows into: " + uri);
}
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// Use this on the URI passed into the function to notify any observers that the uri has
// changed.
getContext().getContentResolver().notifyChange(uri, null);
return returnUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int rows; // Number of rows effected
switch(sUriMatcher.match(uri)){
case GENRE:
rows = db.delete(MovieContract.GenreEntry.TABLE_NAME, selection, selectionArgs);
break;
case MOVIE:
rows = db.delete(MovieContract.MovieEntry.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// Because null could delete all rows:
if(selection == null || rows != 0){
getContext().getContentResolver().notifyChange(uri, null);
}
return rows;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int rows;
switch(sUriMatcher.match(uri)){
case GENRE:
rows = db.update(MovieContract.GenreEntry.TABLE_NAME, values, selection, selectionArgs);
break;
case MOVIE:
rows = db.update(MovieContract.MovieEntry.TABLE_NAME, values, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if(rows != 0){
getContext().getContentResolver().notifyChange(uri, null);
}
return rows;
}
}
I hope it will helps you.
Demo on GitHub: https://github.com/androidessence/MovieDatabase
Full Article : https://guides.codepath.com/android/creating-content-providers
References:
Note : I copied code just because if link of demo or article may be remove in future.