Skip to main content

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
    }
}
Note: The ContentProvider must be declared in the AndroidManifest.xml file with proper permissions and authorities.

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...
}
Note: The 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()
Note: Always close the Cursor after use to prevent memory leaks.
Warning: Never perform database queries on the main thread as it will block the UI and cause ANR (Application Not Responding) errors.

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")
Note: The insert() method must be implemented in your Content Provider subclass to handle the actual insertion logic.
Warning: Ensure that the 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)
Note: The 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>
Note: The 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
val cursor = contentResolver.query(
    uri,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
AsyncTaskLoader Asynchronous loader with background thread support
class MyLoader(context: Context) : AsyncTaskLoader<Cursor>(context) {
    override fun loadInBackground(): Cursor? {
        return context.contentResolver.query(uri, projection, null, null, null)
    }
}

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)
    }
}
Note: When using ContentResolver.query(), always handle the cursor properly to avoid memory leaks.
Warning: Avoid blocking the main thread when querying content providers. Use background threads or 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)
    }
}
Note: Observers are typically registered using ContentResolver.registerObserver().

Key Points

  • notifyChange() is used to signal data changes
  • Observers are notified via ContentResolver
  • Changes can be targeted to specific URIs

Was this article helpful?