Working with Content Providers
1. Creating ContentProvider Class
ContentProviders are used to manage access to a structured set of data in Android. They provide a standard interface that isolates the data from applications.
| Property | Description | Notes |
|---|---|---|
| authority | Unique identifier for the ContentProvider | Must be unique across all apps |
| name | Class name of the ContentProvider | Must match the class name in manifest |
| path | URI path to access the data | Used in ContentUris for querying |
Example: Basic ContentProvider Implementation
import android.content.ContentProvider
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
class MyContentProvider : ContentProvider() {
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
override fun onCreate(): Boolean {
// Initialize the ContentProvider
return true
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
// Handle query operations
return null
}
override fun getType(uri: Uri): String? {
// Return MIME type for the URI
return null
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
// Handle insert operations
return null
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
// Handle delete operations
return 0
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
// Handle update operations
return 0
}
}
2. Defining Content URIs
| URI Component | Description | Example |
|---|---|---|
| Authority | Unique identifier for the content provider | com.example.provider |
| Path | Resource path within the content provider | /contacts |
| ID | Unique identifier for a specific resource | /123 |
Example: Defining Content URI
val CONTACTS_URI = Uri.parse("content://com.example.provider/contacts")
val SINGLE_CONTACT_URI = Uri.parse("content://com.example.provider/contacts/123")
3. Implementing CRUD Methods
Content Providers in Android support the standard CRUD (Create, Read, Update, Delete) operations through specific methods. These methods are defined in the ContentProvider class and must be overridden to implement custom data access logic.
| Method | Description | Return Type | Notes |
|---|---|---|---|
| insert() | Inserts a new row into the content provider | Uri |
Must return the URI of the newly inserted row |
| query() | Retrieves data from the content provider | Cursor |
Used to iterate over the results |
| update() | Updates existing rows in the content provider | int |
Returns number of rows affected |
| delete() | Deletes rows from the content provider | int |
Returns number of rows deleted |
Example: Implementing CRUD in a Content Provider
class MyContentProvider : ContentProvider() {
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val db = dbHelper.writableDatabase
val id = db.insert("my_table", null, values)
return Uri.parse("content://com.example.provider/my_table/" + id)
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
val db = dbHelper.readableDatabase
return db.query(
"my_table",
projection,
selection,
selectionArgs,
null,
null,
sortOrder
)
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
val db = dbHelper.writableDatabase
return db.update("my_table", values, selection, selectionArgs)
}
override fun delete(
uri: Uri,
selection: String?,
selectionArgs: Array<out String>?
): Int {
val db = dbHelper.writableDatabase
return db.delete("my_table", selection, selectionArgs)
}
// Other required methods...
}
uriMatcher is used to determine which operation (insert, query, update, delete) should be performed based on the URI provided. It's essential for handling multiple tables or content types within a single provider.
4. Using ContentResolver
| Method | Description | Return Type | Notes |
|---|---|---|---|
| query() | Queries the content provider for data | Cursor |
Used to retrieve data from a content provider. Requires URI, projection, selection, and selection args |
| insert() | Inserts a new row into the content provider | Uri |
Returns the URI of the newly inserted row. Requires a content URI and ContentValues |
| update() | Updates existing rows in the content provider | Int |
Returns number of rows affected. Requires URI, ContentValues, selection, and selection args |
| delete() | Deletes rows from the content provider | Int |
Returns number of rows deleted. Requires URI, selection, and selection args |
Example: Querying Data
val cursor = contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
null,
null,
null
)
while (cursor?.moveToNext() == true) {
val name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
val number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
Log.d("Contact", "Name: $name, Number: $number")
}
cursor?.close()
Example: Inserting Data
val values = ContentValues().apply {
put(ContactsContract.CommonDataKinds.Phone.NUMBER, "123-456-7890")
put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_HOME)
}
val uri = contentResolver.insert(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
values
)
Log.d("Insert", "Inserted contact at $uri")
Example: Updating Data
val values = ContentValues().apply {
put(ContactsContract.CommonDataKinds.Phone.NUMBER, "098-765-4321")
}
val rowsUpdated = contentResolver.update(
Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, "123"),
values,
null,
null
)
Log.d("Update", "$rowsUpdated rows updated")
Example: Deleting Data
val rowsDeleted = contentResolver.delete(
Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, "123"),
null,
null
)
Log.d("Delete", "$rowsDeleted rows deleted")
5. Querying Content Providers
| Method | Description | Return Type | Notes |
|---|---|---|---|
| query() | Queries the content provider for data | Cursor |
Must be called on a background thread to avoid blocking the UI |
| query() | Queries with projection, selection, and sort order | Cursor |
Allows filtering and sorting of data |
Example: Querying a Content Provider
val cursor = contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
arrayOf(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME),
null,
null,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
)
while (cursor?.moveToNext() == true) {
val name = cursor.getString(
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
)
Log.d("Contact", "Name: $name")
}
cursor?.close()
Cursor after use to prevent memory leaks.
6. Inserting Data
Content Providers support inserting data using the insert() method. This method is typically used to add new records to the underlying data store.
| Method | Description | Return Type | Notes |
|---|---|---|---|
| insert() | Inserts a new row into the content provider | Uri |
Returns the URI of the newly inserted row |
Example: Inserting Data into a Content Provider
val contentValues = ContentValues().apply {
put("name", "John Doe")
put("email", "john@example.com")
}
val uri = contentResolver.insert(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
contentValues
)
Log.d("Insert", "Inserted row at: $uri")
insert() method must be implemented in your Content Provider subclass to handle the actual insertion logic.
Uri passed to insert() matches the content URI of your provider, or it will not be processed correctly.
7. Updating and Deleting Data
| Method | Description | Return Type | Notes |
|---|---|---|---|
| update() | Updates rows in a content provider | Int |
Requires URI, ContentValues, and optional selection/args |
| delete() | Deletes rows from a content provider | Int |
Requires URI, selection, and optional selection args |
Example: Updating Data
val uri = Uri.parse("content://com.example.provider/users")
val values = ContentValues().apply {
put("name", "John Doe")
put("email", "john@example.com")
}
val selection = "id = ?"
val selectionArgs = arrayOf("123")
val rowsUpdated = contentResolver.update(uri, values, selection, selectionArgs)
Example: Deleting Data
val uri = Uri.parse("content://com.example.provider/users")
val selection = "id = ?"
val selectionArgs = arrayOf("123")
val rowsDeleted = contentResolver.delete(uri, selection, selectionArgs)
update() and delete() methods return the number of rows affected. These operations are asynchronous and should be performed on a background thread to avoid blocking the UI.
8. Registering Providers in Manifest
| Field | Description | Value |
|---|---|---|
| android:name | Full class name of the ContentProvider | com.example.MyContentProvider |
| android:authorities | Unique identifier for the provider | com.example.provider |
| android:exported | Whether the provider can be accessed by other apps | false (default for internal providers) |
| android:enabled | Whether the provider is enabled at runtime | true |
| android:permission | Permission required to access the provider | com.example.permission.READ_DATA |
Example: AndroidManifest.xml Registration
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<provider
android:name=".MyContentProvider"
android:authorities="com.example.provider"
android:exported="false"
android:enabled="true" />
</application>
</manifest>
authorities attribute must be unique across all apps to avoid conflicts.
9. Using CursorLoader Alternative
As of Android API level 28, CursorLoader is deprecated. The recommended alternative is to use ContentResolver with query() or LoaderManager with AsyncTaskLoader.
| Feature | Description | Implementation |
|---|---|---|
| ContentResolver.query() | Query content providers directly | |
| AsyncTaskLoader | Asynchronous loader with background thread support | |
Example: Using ContentResolver.query()
val cursor = contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
arrayOf(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME),
null,
null,
null
)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
println(name)
}
}
ContentResolver.query(), always handle the cursor properly to avoid memory leaks.
AsyncTaskLoader.
10. Observing Content Changes
Content Providers can notify observers when data changes. This is done using the notifyChange() method.
| Method | Description | Notes |
|---|---|---|
| notifyChange() | Triggers observers to update their data | Must be called from within a ContentProvider |
| notifyChange(Uri uri) | Triggers observers for a specific URI | Useful for targeted updates |
Example: Observing Content Changes
class MyContentProvider : ContentProvider() {
override fun notifyChange(uri: Uri) {
context?.contentResolver?.notifyChange(uri, null)
}
fun updateData(newData: String) {
// Update data in database
notifyChange(MyContract.CONTENT_URI)
}
}
ContentResolver.registerObserver().