The Android framework uses a concept called content providers to enable applications to share and use data across the platform. A content provider is an owner of particular content, it provides well defined APIs to read, insert, update and delete that data. The content provider can internally use any place to store its data like a local file, local database or some remote service.
A content provider component supplies data from one application to others on request. Such requests are handled by the methods of the ContentResolver class. A content provider can use different ways to store its data and the data can be stored in a database, in files, or even over a network.
Content providers let you centralize content in one place and have many different applications access it as needed. A content provider behaves very much like a database where you can query it, edit its content, as well as add or delete content using insert()
, update()
, delete()
, and query()
methods. In most cases this data is stored in an SQlite database.
A content provider is implemented as a subclass of ContentProvider
class and must implement a standard set of APIs that enable other applications to perform transactions.
public class MyContentProvider extends ContentProvider { }
Content providers create an abstraction layer between its repository of data and external application that are using data. External Application can call ContentProvider
methods with the use of ContentResolver
. ContentResolver
work as ContentProvider
client object, with the use of ContentResolver
object we can get data from ContentProvider
.
To query a content provider, you specify the query string in the form of a URI which has following format
<prefix>://<authority>/<data_type>/<id>
Here are the details of various parts of the URI
Contacts
content provider, then the data path would be people and URI would look like this content://contacts/people.Contacts
content provider then URI would look like this content://contacts/people/5.A ContentProvider
usually has 3 components, a constants class that defines the schema for your tables and your content authority, an SQLite helper class that physically creates the table, and the provider class, in which you implement the URiMatcher and your CRUD methods.
Creating of ContentProvider
involves number of simple steps to create your own content provider.
ContentProvider
class that extends the ContentProvider
base class.onCreate()
method which will use SQLite Open Helper method to create or open the provider's database. When your application is launched, the onCreate()
handler of each of its content providers is called on the main application thread.ContentProvider
queries to perform different database specific operations.ContentProvider
in your activity file using <provider> tag.Here is the list of methods which you need to override in ContentProvider
class to have your ContentProvider
working.
onCreate()
method is called when the provider is started.query()
method receives a request from a client. The result is returned as a Cursor
object.insert()
method inserts a new record into the content provider.delete()
method deletes an existing record from the content provider.update()
method updates an existing record from the content provider.getType()
method returns the MIME type of the data at the given URI.To retrieve the data, we need to follow these step
Following example will explain you how to create your own ContentProvider
.
First, modify main activity file MainActivity.java to add two new methods addMovie()
and getMovies()
.
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void addMovie(View view) { // add a new movie record String title = ((EditText)findViewById(R.id.etTitle)).getText().toString(); int year = Integer.valueOf(((EditText)findViewById(R.id.etYear)).getText().toString()); ContentValues values = new ContentValues(); values.put(MovieProvider.TITLE, title); values.put(MovieProvider.YEAR, year); Uri uri = getContentResolver().insert(MovieProvider.CONTENT_URI, values); Toast.makeText(getBaseContext(), uri.toString(), Toast.LENGTH_LONG).show(); } public void getMovies(View view) { // retrieve movie records String URL = "content://" + MovieProvider.PROVIDER_NAME; Uri moviesUri = Uri.parse(URL); String orderBy = MovieProvider.TITLE; Cursor c = getContentResolver().query(moviesUri, null, null, null, orderBy); if (c.moveToFirst()) { String id, title, year; do { id = c.getString(c.getColumnIndex(MovieProvider.ID)); title = c.getString(c.getColumnIndex(MovieProvider.TITLE)); year = c.getString(c.getColumnIndex(MovieProvider.YEAR)); Toast.makeText(this, id + ", " + title + ", " + year, Toast.LENGTH_SHORT).show(); } while (c.moveToNext()); } } }
Create a new java file called MovieProvider.java to define your actual provider and associated methods. Inside of our MovieProvider
class, we need to define a few properties: a content authority (PROVIDER_NAME
), which is a unique identifier for our database and a base URI (CONTENT_URI
).
public class MovieProvider extends ContentProvider { static final String PROVIDER_NAME = "me.proft.MovieProvider"; static final String URL = "content://" + PROVIDER_NAME + "/movies"; static final Uri CONTENT_URI = Uri.parse(URL); static final String ID = "id"; static final String TITLE = "title"; static final String YEAR = "year"; private static HashMap<String, String> MOVIES_PROJECTION_MAP; static final int MOVIES = 1; static final int MOVIE_ID = 2; static final UriMatcher uriMatcher; static{ uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "movies", MOVIES); uriMatcher.addURI(PROVIDER_NAME, "movies/#", MOVIE_ID); } // database specific constant declarations private SQLiteDatabase db; static final String DATABASE_NAME = "Movie"; static final String TABLE_NAME = "movies"; static final int DATABASE_VERSION = 1; static final String CREATE_DB_TABLE = " CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, " + " title TEXT NOT NULL, " + " year INTEGER NOT NULL);"; // helper class that actually creates and manages the provider's underlying data repository. private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context){ super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_DB_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); onCreate(db); } } @Override public boolean onCreate() { Context context = getContext(); DatabaseHelper dbHelper = new DatabaseHelper(context); // create a write able database which will trigger its creation if it doesn't already exist. db = dbHelper.getWritableDatabase(); return (db == null)? false:true; } @Override public Uri insert(Uri uri, ContentValues values) { // add a new movie record long rowID = db.insert(TABLE_NAME, "", values); // if record is added successfully if (rowID > 0) { Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID); getContext().getContentResolver().notifyChange(_uri, null); return _uri; } throw new SQLException("Failed to add a record into " + uri); } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_NAME); switch (uriMatcher.match(uri)) { case MOVIES: qb.setProjectionMap(MOVIES_PROJECTION_MAP); break; case MOVIE_ID: qb.appendWhere(ID + "=" + uri.getPathSegments().get(1)); break; default: } if (sortOrder == null || sortOrder == ""){ // by default sort on movie title sortOrder = TITLE; } Cursor c = qb.query(db, projection, selection, selectionArgs,null, null, sortOrder); // register to watch a content URI for changes c.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; switch (uriMatcher.match(uri)){ case MOVIES: count = db.delete(TABLE_NAME, selection, selectionArgs); break; case MOVIE_ID: String id = uri.getPathSegments().get(1); count = db.delete(TABLE_NAME, ID + " = " + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; switch (uriMatcher.match(uri)) { case MOVIES: count = db.update(TABLE_NAME, values, selection, selectionArgs); break; case MOVIE_ID: count = db.update(TABLE_NAME, values, ID + " = " + uri.getPathSegments().get(1) + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri ); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)){ // get all movie records case MOVIES: return "vnd.android.cursor.dir/vnd.me.proft.movies"; // get a particular student case MOVIE_ID: return "vnd.android.cursor.item/vnd.me.proft.movies"; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } } }
To deal with multiple URIs Android provides the helper class UriMatcher
. This class eases the parsing of URIs.
The query()
method takes in five parameters:
uri
. The URI (or table) that should be queried.projection
. A string array of columns that will be returned in the result set.selection
. A string defining the criteria for results to be returned.selectionArgs
. Arguments to the above criteria that rows will be checked against.sortOrder
. A string of the column(s) and order to sort the result set by.The insert()
method takes in a ContentValues
object, which is a key value pair of column names and values to be inserted.
The update()
and delete()
methods take in a selection string and arguments to define which rows should be updated or deleted. They differ in that the update method requires a ContentProvider
object as well, for the columns in that row(s) that will be updated.
The getType
method is used to find the MIME type of the results, either a directory of multiple results, or an individual item.
Register your content provider in your AndroidManifest.xml file using
<manifest ... <application ... <provider android:name="MovieProvider" android:authorities="me.proft.MovieProvider"/> </application> </manifest>
Modify the default content of activity_main.xml file.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="2dp"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Title" android:id="@+id/etTitle"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Year" android:id="@+id/etYear"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnAdd" android:text="Add movie" android:onClick="addMovie"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnGetMovie" android:text="Get movie" android:onClick="getMovies"/> </LinearLayout>
Run the application to launch Android emulator and verify the result of the changes done in the application.