Android - Content Provider
The Content Provider component provides data from one application to others upon request. These requests are handled by methods of the ContentResolver class. Content Providers can store data in various ways, such as in databases, files, or even over the network.
Sometimes, sharing data between applications is necessary, and Content Providers become very useful in such scenarios.
Content Providers allow content to be centralized, enabling multiple different applications to access it when needed. Their behavior is similar to databases. You can query and edit their content, using methods like insert()
, update()
, delete()
, and query()
to add or remove content. Most often, data is stored in an SQLite database.
Content Providers are implemented as subclasses of the ContentProvider class and need to implement a set of standard APIs to allow other applications to perform transactions.
public class MyApplication extends ContentProvider {
}
Content URI
To query a Content Provider, you need to specify the query string in the form of a URI with the following format:
<prefix>://<authority>/<data_type>/<id>
Here is a detailed explanation of each part of the URI:
Part | Description |
---|---|
prefix | Prefix: Always set to content:// |
authority | Authority: Specifies the name of the Content Provider, such as contacts , browser , etc. For third-party Content Providers, it can be the full name, like cn.programmer.statusprovider |
data_type | Data Type: This indicates the type of data in this particular Content Provider. For example, if you want to get all contacts via the Content Provider Contacts , the data path is people , and the URI would be: content://contacts/people |
id | This specifies the particular record requested. For example, if you are looking for the contact with ID 5 in the Content Provider Contacts , the URI would look like this: content://contacts/people/5 |
Creating a Content Provider
Here are the simple steps to create your own Content Provider:
- First, you need to extend the ContentProviderbase class to create a Content Provider class.
- Next, you need to define the URI address for your Content Provider to access content.
- Then, you need to create a database to hold the content. Usually, Android uses an SQLite database and overrides the
onCreate()
method to create or open the provider's database using methods from SQLiteOpenHelper. When your application starts, theonCreate()
method of each Content Provider is called on the application's main thread. - Finally, register the Content Provider in
AndroidManifest.xml
using the<provider.../>
tag.
Here are some methods you need to override in the ContentProvider class to make your Content Provider work:
onCreate()
: Called when the provider is started.query()
: This method receives requests from the client. The result is returned as a Cursor object.insert()
: This method inserts new records into the Content Provider.delete()
: This method deletes existing records from the Content Provider.update()
: This method updates existing records in the Content Provider.getType()
: This method returns the MIME type of the data at the given URI.
Example
This example explains how to create your own Content Provider. Let's follow these steps:
Step | Description |
---|---|
1 | Create an Android application using Android Studio, named Content Provider , under the package com.tutorialpro.contentprovider , and set up an empty activity. |
2 | Modify the main activity file MainActivity.java to add two new methods, onClickAddName() and onClickRetrieveStudents() . |
3 | Create a new Java file StudentsProvider.java under the package com.tutorialpro.contentprovider to define the actual provider and associated methods. |
4 | Register the Content Provider in AndroidManifest.xml using the <provider.../> tag. |
5 | Modify the res/layout/activity_main.xml file to include a simple interface for adding student records. |
6 | No changes are needed for strings.xml , as Android Studio handles this file. |
7 | Start the Android emulator to run the application and verify the results of the changes made. |
Below is the content of the modified main activity file src/com.tutorialpro.contentprovider/MainActivity.java
. This file includes basic lifecycle methods and two new methods, onClickAddName()
and onClickRetrieveStudents()
, to handle user interactions.
package com.tutorialpro.contentprovider;
import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentValues;
import android.content.CursorLoader;
import android.database.Cursor;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.tutorialpro.contentprovider.R;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
public void onClickAddName(View view) {
// Add a new student record
ContentValues values = new ContentValues();
values.put(StudentsProvider.NAME,
((EditText)findViewById(R.id.editText2)).getText().toString());
values.put(StudentsProvider.GRADE,
((EditText)findViewById(R.id.editText3)).getText().toString());
Uri uri = getContentResolver().insert(
StudentsProvider.CONTENT_URI, values);
Toast.makeText(getBaseContext(),
uri.toString(), Toast.LENGTH_LONG).show();
}
public void onClickRetrieveStudents(View view) {
// Retrieve student records
String URL = "content://com.example.provider.College/students";
Uri students = Uri.parse(URL);
Cursor c = managedQuery(students, null, null, null, "name");
if (c.moveToFirst()) {
do{
Toast.makeText(this,
c.getString(c.getColumnIndex(StudentsProvider._ID)) +
", " + c.getString(c.getColumnIndex( StudentsProvider.NAME)) +
", " + c.getString(c.getColumnIndex( StudentsProvider.GRADE)),
Toast.LENGTH_SHORT).show();
} while (c.moveToNext());
}
}
}
Create a new file StudentsProvider.java
under the package com.tutorialpro.contentprovider
. Below is the content of src/com.tutorialpro.contentprovider/StudentsProvider.java
.
package com.tutorialpro.contentprovider;
import java.util.HashMap;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
public class StudentsProvider extends ContentProvider {
static final String PROVIDER_NAME = "com.example.provider.College";
static final String URL = "content://" + PROVIDER_NAME + "/students"; static final Uri CONTENT_URI = Uri.parse(URL);
static final String _ID = "_id"; static final String NAME = "name"; static final String GRADE = "grade";
private static HashMap<String, String> STUDENTS_PROJECTION_MAP;
static final int STUDENTS = 1; static final int STUDENT_ID = 2;
static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "students", STUDENTS); uriMatcher.addURI(PROVIDER_NAME, "students/#", STUDENT_ID); }
/**
- Database specific constant declarations */ private SQLiteDatabase db; static final String DATABASE_NAME = "College"; static final String STUDENTS_TABLE_NAME = "students"; static final int DATABASE_VERSION = 1; static final String CREATE_DB_TABLE = " CREATE TABLE " + STUDENTS_TABLE_NAME + " (_id INTEGER PRIMARY KEY AUTOINCREMENT, " + " name TEXT NOT NULL, " + " grade TEXT NOT NULL);";
/**
- Helper class that actually creates and manages the provider's underlying data source. */ 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 " + STUDENTS_TABLE_NAME);
onCreate(db);
}
}
@Override public boolean onCreate() { Context context = getContext(); DatabaseHelper dbHelper = new DatabaseHelper(context);
/**
* Create a writeable database if it does not already exist.
*/
db = dbHelper.getWritableDatabase();
return (db == null) ? false : true;
}
@Override public Uri insert(Uri uri, ContentValues values) { /** * Add a new student record */ long rowID = db.insert(STUDENTS_TABLE_NAME, "", values);
/**
* If the 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(STUDENTS_TABLE_NAME);
switch (uriMatcher.match(uri)) {
case STUDENTS:
qb.setProjectionMap(STUDENTS_PROJECTION_MAP);
break;
case STUDENT_ID:
qb.appendWhere(_ID + "=" + uri.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (sortOrder == null || sortOrder == "") {
/**
* By default, sort by student name
*/
sortOrder = NAME;
}
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 STUDENTS:
count = db.delete(STUDENTS_TABLE_NAME, selection, selectionArgs);
break;
case STUDENT_ID:
String id = uri.getPathSegments().get(1);
count = db.delete(STUDENTS_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 STUDENTS:
count = db.update(STUDENTS_TABLE_NAME, values, selection, selectionArgs);
break;
case STUDENT_ID:
count = db.update(STUDENTS_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)) { case STUDENTS: return "vnd.android.cursor.dir/vnd.example.students";
case STUDENT_ID:
return "vnd.android.cursor.item/vnd.example.students";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
/**
* Get all student records
*/
case STUDENTS:
return "vnd.android.cursor.dir/vnd.example.students";
/**
* Get a specific student
*/
case STUDENT_ID:
return "vnd.android.cursor.item/vnd.example.students";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
Here is the modified AndroidManifest.xml file. A <provider.../> tag has been added here to include our content provider:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tutorialpro.contentprovider"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="22" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.tutorialpro.contentprovider.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider android:name="StudentsProvider"
android:authorities="com.example.provider.College" >
</provider>
</application>
</manifest>
Below is the content of the res/layout/activity_main.xml file:
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Content Provider Example"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:textSize="30dp" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="www.tutorialpro.org"
android:textColor="#ff87ff09"
android:textSize="30dp"
android:layout_below="@+id/textView1"
android:layout_centerHorizontal="true" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageButton"
android:src="@drawable/ic_launcher"
android:layout_below="@+id/textView2"
android:layout_centerHorizontal="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button2"
android:text="Add"
android:layout_below="@+id/editText3"
android:layout_alignRight="@+id/textView2"
android:layout_alignEnd="@+id/textView2"
android:layout_alignLeft="@+id/textView2"
android:layout_alignStart="@+id/textView2"
android:onClick="onClickAddName"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editText"
android:layout_below="@+id/imageButton"
android:layout_alignRight="@+id/imageButton"
android:layout_alignEnd="@+id/imageButton" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editText2"
android:layout_alignTop="@+id/editText"
android:layout_alignLeft="@+id/textView1"
android:layout_alignStart="@+id/textView1"
android:layout_alignRight="@+id/textView1"
android:layout_alignEnd="@+id/textView1"
android:hint="Name"
android:textColorHint="@android:color/holo_blue_light" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editText3"
android:layout_below="@+id/editText"
android:layout_alignLeft="@+id/editText2"
android:layout_alignStart="@+id/editText2"
android:layout_alignRight="@+id/editText2"
android:layout_alignEnd="@+id/editText2"
android:hint="Grade"
android:textColorHint="@android:color/holo_blue_bright" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Query"
android:id="@+id/button"
android:layout_below="@+id/button2"
android:layout_alignRight="@+id/editText3"
android:layout_alignEnd="@+id/editText3"
android:layout_alignLeft="@+id/button2"
android:layout_alignStart="@+id/button2"
android:onClick="onClickRetrieveStudents"/>
</RelativeLayout>
Ensure that the res/values/strings.xml file contains the following:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Content Provider</string>
<string name="action_settings">Settings</string>
</resources>
Let's run the Content Provider application we just modified. I assume you have already created an AVD during the setup environment. Open the activity file in your project and click on the toolbar.
Enter a name and grade, and click the "Add" button, which will add a student record to the data and delete a message at the bottom. The message content displays the content provider URI containing the number of records added to the database. This operation uses the insert() method. Repeat this process to add more students to our content provider's database.
Once you have finished adding database records, it's time to request the records back from the content provider. Click the "Query" button, which will retrieve and display all data records through the implemented query() method.
You can provide callback methods in MainActivity.java to write update and delete operations, and modify the user interface to add update and delete functions.
You can use existing content providers, such as contacts, in this way. You can also develop an excellent database-oriented application in this way, performing all database operations such as read, write, update, and delete as demonstrated in the example above.