A content provider manages access to a central repository of data. A provider is part of an Android application, which often provides its own UI for working with the data. However, content providers are primarily intended to be used by other applications, which access the provider using a provider-client object.
How to use a Content provider in Application
We use content providers for accessing, sharing the data among the applications.
1- Accessing the Provider
When you want to access data in a content provider, you use the ContentResolver object in your application’s Context to communicate with the provider as a client.
2- Register the provider class in Manifest.xml
<provider android:authorities="learnprogramming.academy.tasktimer.provider" android:name="learnprogramming.academy.tasktimer.AppProvider" android:exported="false"/>
3- Create a model class for the data
@Parcelize data class Task(val name: String, val description: String, val sortOrder: Int, var id: Long = 0) : Parcelable { }
4- Create an object class
object TasksContract { internal const val TABLE_NAME = "Tasks" /** * The URI to access the Tasks table. */ val CONTENT_URI: Uri = Uri.withAppendedPath(CONTENT_AUTHORITY_URI, TABLE_NAME) const val CONTENT_TYPE = "vnd.android.cursor.dir/vnd.$CONTENT_AUTHORITY.$TABLE_NAME" const val CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.$CONTENT_AUTHORITY.$TABLE_NAME" // Tasks fields object Columns { const val ID = BaseColumns._ID const val TASK_NAME = "Name" const val TASK_DESCRIPTION = "Description" const val TASK_SORT_ORDER = "SortOrder" } fun getId(uri: Uri): Long { return ContentUris.parseId(uri) } fun buildUriFromId(id: Long): Uri { return ContentUris.withAppendedId(CONTENT_URI, id) } }
5- Create a provider class
const val CONTENT_AUTHORITY = "learnprogramming.academy.tasktimer.provider" val CONTENT_AUTHORITY_URI: Uri = Uri.parse("content://$CONTENT_AUTHORITY") private const val TASKS = 100 private const val TASKS_ID = 101 class AppProvider: ContentProvider() { private val uriMatcher by lazy { buildUriMatcher() } private fun buildUriMatcher() : UriMatcher { Log.d(TAG, "buildUriMatcher: starts") val matcher = UriMatcher(UriMatcher.NO_MATCH) // e.g. content://learnprogramming.academy.tasktimer.provider/Tasks matcher.addURI(CONTENT_AUTHORITY, TasksContract.TABLE_NAME, TASKS) // e.g. content://learnprogramming.academy.tasktimer.provider/Tasks/8 matcher.addURI(CONTENT_AUTHORITY, "${TasksContract.TABLE_NAME}/#", TASKS_ID) return matcher } override fun onCreate(): Boolean { Log.d(TAG, "onCreate: starts") return true } override fun getType(uri: Uri): String { val match = uriMatcher.match(uri) return when (match) { TASKS -> TasksContract.CONTENT_TYPE TASKS_ID -> TasksContract.CONTENT_ITEM_TYPE else -> throw IllegalArgumentException("unknown Uri: $uri") } } override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor { Log.d(TAG, "query: called with uri $uri") val match = uriMatcher.match(uri) Log.d(TAG, "query: match is $match") val queryBuilder = SQLiteQueryBuilder() when (match) { TASKS -> queryBuilder.tables = TasksContract.TABLE_NAME TASKS_ID -> { queryBuilder.tables = TasksContract.TABLE_NAME val taskId = TasksContract.getId(uri) queryBuilder.appendWhere("${TasksContract.Columns.ID} = ") queryBuilder.appendWhereEscapeString("$taskId") } else -> throw IllegalArgumentException("Unknown URI: $uri") } val db = AppDatabase.getInstance(context).readableDatabase val cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder) Log.d(TAG, "query: rows in returned cursor = ${cursor.count}") // TODO remove this line return cursor } override fun insert(uri: Uri, values: ContentValues): Uri { Log.d(TAG, "insert: called with uri $uri") val match = uriMatcher.match(uri) Log.d(TAG, "insert: match is $match") val recordId: Long val returnUri: Uri when(match) { TASKS -> { val db = AppDatabase.getInstance(context).writableDatabase recordId = db.insert(TasksContract.TABLE_NAME, null, values) if(recordId != -1L) { returnUri = TasksContract.buildUriFromId(recordId) } else { throw SQLException("Failed to insert, Uri was $uri") } } else -> throw IllegalArgumentException("Unknown uri: $uri") } if (recordId > 0) { // something was inserted Log.d(TAG, "insert: Setting notifyChange with $uri") context?.contentResolver?.notifyChange(uri, null) } Log.d(TAG, "Exiting insert, returning $returnUri") return returnUri } override fun update(uri: Uri, values: ContentValues, selection: String?, selectionArgs: Array<out String>?): Int { Log.d(TAG, "update: called with uri $uri") val match = uriMatcher.match(uri) Log.d(TAG, "update: match is $match") val count: Int var selectionCriteria: String when(match) { TASKS -> { val db = AppDatabase.getInstance(context).writableDatabase count = db.update(TasksContract.TABLE_NAME, values, selection, selectionArgs) } TASKS_ID -> { val db = AppDatabase.getInstance(context).writableDatabase val id = TasksContract.getId(uri) selectionCriteria = "${TasksContract.Columns.ID} = $id" if(selection != null && selection.isNotEmpty()) { selectionCriteria += " AND ($selection)" } count = db.update(TasksContract.TABLE_NAME, values, selectionCriteria, selectionArgs) } else -> throw IllegalArgumentException("Unknown uri: $uri") } if (count > 0) { // something was updated Log.d(TAG, "update: Setting notifyChange with $uri") context?.contentResolver?.notifyChange(uri, null) } Log.d(TAG, "Exiting update, returning $count") return count } override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { Log.d(TAG, "delete: called with uri $uri") val match = uriMatcher.match(uri) Log.d(TAG, "delete: match is $match") val count: Int var selectionCriteria: String when(match) { TASKS -> { val db = AppDatabase.getInstance(context).writableDatabase count = db.delete(TasksContract.TABLE_NAME, selection, selectionArgs) } TASKS_ID -> { val db = AppDatabase.getInstance(context).writableDatabase val id = TasksContract.getId(uri) selectionCriteria = "${TasksContract.Columns.ID} = $id" if(selection != null && selection.isNotEmpty()) { selectionCriteria += " AND ($selection)" } count = db.delete(TasksContract.TABLE_NAME, selectionCriteria, selectionArgs) } else -> throw IllegalArgumentException("Unknown uri: $uri") } if (count > 0) { // something was deleted Log.d(TAG, "delete: Setting notifyChange with $uri") context?.contentResolver?.notifyChange(uri, null) } Log.d(TAG, "Exiting delete, returning $count") return count } }
6- When we want to store the data in the content provider
fun saveTask(task: Task): Task { val values = ContentValues() if (task.name.isNotEmpty()) { // Don't save a task with no name values.put(TasksContract.Columns.TASK_NAME, task.name) values.put(TasksContract.Columns.TASK_DESCRIPTION, task.description) values.put(TasksContract.Columns.TASK_SORT_ORDER, task.sortOrder) // defaults to zero if empty if (task.id == 0L) { GlobalScope.launch { Log.d(TAG, "saveTask: adding new task") val uri = getApplication<Application>().contentResolver?.insert(TasksContract.CONTENT_URI, values) if (uri != null) { task.id = TasksContract.getId(uri) Log.d(TAG, "saveTask: new id is ${task.id}") } } } else { // task has an id, so we're updating GlobalScope.launch { Log.d(TAG, "saveTask: updating task") getApplication<Application>().contentResolver?.update(TasksContract.buildUriFromId(task.id), values, null, null) } } } return task }
7- When we want to fetch the data using a content provider
private fun loadTasks() { val projection = arrayOf(TasksContract.Columns.ID, TasksContract.Columns.TASK_NAME, TasksContract.Columns.TASK_DESCRIPTION, TasksContract.Columns.TASK_SORT_ORDER) // <order by> Tasks.SortOrder, Tasks.Name val sortOrder = "${TasksContract.Columns.TASK_SORT_ORDER}, ${TasksContract.Columns.TASK_NAME}" GlobalScope.launch { val cursor = getApplication<Application>().contentResolver.query( TasksContract.CONTENT_URI, projection, null, null, sortOrder) databaseCursor.postValue(cursor) } }
8- When we want to insert new data in a content provider
fun saveTask(task: Task): Task { val values = ContentValues() if (task.name.isNotEmpty()) { // Don't save a task with no name values.put(TasksContract.Columns.TASK_NAME, task.name) values.put(TasksContract.Columns.TASK_DESCRIPTION, task.description) values.put(TasksContract.Columns.TASK_SORT_ORDER, task.sortOrder) // defaults to zero if empty if (task.id == 0L) { GlobalScope.launch { Log.d(TAG, "saveTask: adding new task") val uri = getApplication<Application>().contentResolver?.insert(TasksContract.CONTENT_URI, values) if (uri != null) { task.id = TasksContract.getId(uri) Log.d(TAG, "saveTask: new id is ${task.id}") } } } else { // task has an id, so we're updating GlobalScope.launch { Log.d(TAG, "saveTask: updating task") getApplication<Application>().contentResolver?.update(TasksContract.buildUriFromId(task.id), values, null, null) } } } return task }
9- When we want to delete the task
fun deleteTask(taskId: Long) { Log.d(TAG, "Deleting task") GlobalScope.launch { getApplication<Application>().contentResolver?.delete(TasksContract.buildUriFromId(taskId), null, null) } }