ContentProvider, FileProvider & URI Permissions
Secure data and file sharing between Android apps
Open interactive version (quiz + challenge)Real-world analogy
What is it?
ContentProvider is Android's structured data-sharing mechanism between apps, built on a URI-addressed CRUD interface backed by SQLite or any other data source. FileProvider is a ContentProvider subclass that securely exposes files via content:// URIs with time-limited URI permission grants, replacing the dangerous file:// URI pattern. Together they form Android's inter-app data exchange system.
Real-world relevance
A camera app uses FileProvider to share a captured photo with a crop app — it generates a content:// URI, attaches FLAG_GRANT_READ_URI_PERMISSION to the Intent, and the crop app reads the file without ever knowing its path. A contacts app exposes data through ContentProvider so that a dialer or messaging app can query phone numbers by name.
Key points
- ContentProvider purpose — ContentProvider exposes structured data (usually from a SQLite database) to other apps via a standardized CRUD interface. It uses URI-based addressing: content://authority/table/id. The system routes queries through the provider, enforcing declared permissions. It is still the correct mechanism for sharing data with system components like the Contacts app or MediaStore.
- ContentResolver — the client side — Apps never talk to a ContentProvider directly. They use ContentResolver (obtained via context.contentResolver) to issue query(), insert(), update(), delete(), and openInputStream() calls. The system resolves the URI to the correct provider and routes the call, handling cross-process marshalling transparently.
- CRUD operations through ContentProvider — query() returns a Cursor. insert() returns the URI of the new row. update() and delete() return the number of affected rows. All four must be thread-safe because multiple apps can call them concurrently. SQLiteDatabase handles this internally, but custom providers must synchronize themselves.
- Declaring a ContentProvider — Declare in AndroidManifest with android:authorities (globally unique, typically your package name), android:exported (true if other apps need access), and android:readPermission / android:writePermission. Without exported=true and correct permissions, other apps cannot use it.
- FileProvider — secure file sharing — FileProvider is a special ContentProvider subclass that maps file paths to content:// URIs. It prevents exposing file:// URIs (which are blocked by FileUriExposedException since API 24) and instead grants time-limited, permission-scoped access to files. Essential for sharing photos, documents, and APKs with other apps or system intents.
- Configuring FileProvider — Declare FileProvider in Manifest with android:authorities, android:exported='false', and android:grantUriPermissions='true'. Provide an XML file-paths resource that maps logical names to real directory paths (e.g., files-path, cache-path, external-path). Never expose the raw file system path — use logical names only.
- URI permission grants — FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION are added to Intents when sharing a file. These grants are temporary and scoped to the receiving app's process. They expire when the receiving task stack is cleared. This is the correct way to give another app access to your private files without making them world-readable.
- getUriForFile() — the key FileProvider call — FileProvider.getUriForFile(context, authority, file) converts a File object to a content:// URI. You then set this URI on an Intent along with the URI permission flags. Never pass a File path as a String extra — it bypasses the permission system entirely.
- MediaStore — the system ContentProvider — MediaStore is Android's built-in ContentProvider for photos, videos, audio, and documents. In Android 10+ (scoped storage), your app can only access its own files and files the user explicitly picks via MediaStore queries or Storage Access Framework. Know how to query MediaStore for images and how to insert new media correctly.
- When ContentProvider is still the right choice in 2026 — System integrations (contacts, calendar, media), sharing structured data with third-party apps via a documented API, and search suggestions (SearchRecentSuggestionsProvider). For sharing within your own app across processes, prefer a database + ViewModel + repository pattern. ContentProvider adds complexity that is rarely justified for single-app use.
- CursorLoader deprecation awareness — CursorLoader was the recommended way to load ContentProvider data asynchronously in the Loader API era. It is now deprecated. Use viewModelScope + coroutines + contentResolver queries on a background dispatcher instead. Know this transition for legacy codebase discussions.
- Security pitfalls — Common bugs: setting exported=true on a ContentProvider without permissions (exposes all data to any app), using file:// URIs directly (causes crash on API 24+), not adding URI permission flags to the Intent (receiving app gets the URI but cannot read it). Mentioning these shows security awareness that impresses senior interviewers.
Code example
// FileProvider setup in AndroidManifest.xml (shown as comment)
// <provider
// android:name="androidx.core.content.FileProvider"
// android:authorities="com.example.app.fileprovider"
// android:exported="false"
// android:grantUriPermissions="true">
// <meta-data
// android:name="android.support.FILE_PROVIDER_PATHS"
// android:resource="@xml/file_paths" />
// </provider>
// res/xml/file_paths.xml (shown as comment)
// <paths>
// <cache-path name="camera_images" location="images/" />
// <files-path name="documents" location="docs/" />
// </paths>
// Sharing a file using FileProvider
fun sharePhoto(context: Context, photoFile: File) {
val photoUri: Uri = FileProvider.getUriForFile(
context,
"com.example.app.fileprovider",
photoFile
)
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "image/jpeg"
putExtra(Intent.EXTRA_STREAM, photoUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(shareIntent, "Share Photo"))
}
// Launching camera and saving to FileProvider URI
fun launchCamera(activity: AppCompatActivity, launcher: ActivityResultLauncher<Uri>): Uri {
val imageFile = File(activity.cacheDir, "images/capture_${System.currentTimeMillis()}.jpg")
imageFile.parentFile?.mkdirs()
val photoUri = FileProvider.getUriForFile(
activity,
"com.example.app.fileprovider",
imageFile
)
launcher.launch(photoUri)
return photoUri
}
// Custom ContentProvider skeleton
class NotesProvider : ContentProvider() {
private lateinit var dbHelper: NotesDatabaseHelper
override fun onCreate(): Boolean {
dbHelper = NotesDatabaseHelper(context!!)
return true
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
val db = dbHelper.readableDatabase
val cursor = db.query("notes", projection, selection, selectionArgs, null, null, sortOrder)
cursor.setNotificationUri(context!!.contentResolver, uri)
return cursor
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val db = dbHelper.writableDatabase
val id = db.insert("notes", null, values)
context!!.contentResolver.notifyChange(uri, null)
return ContentUris.withAppendedId(CONTENT_URI, id)
}
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
val db = dbHelper.writableDatabase
val count = db.update("notes", values, selection, selectionArgs)
context!!.contentResolver.notifyChange(uri, null)
return count
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val db = dbHelper.writableDatabase
val count = db.delete("notes", selection, selectionArgs)
context!!.contentResolver.notifyChange(uri, null)
return count
}
override fun getType(uri: Uri): String = "vnd.android.cursor.dir/vnd.com.example.notes"
companion object {
val CONTENT_URI: Uri = Uri.parse("content://com.example.notes.provider/notes")
}
}
// Querying MediaStore for images (scoped storage, API 29+)
fun getPhotosFromMediaStore(context: Context): List<Uri> {
val uris = mutableListOf<Uri>()
val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME)
context.contentResolver.query(collection, projection, null, null, "${MediaStore.Images.Media.DATE_ADDED} DESC")?.use { cursor ->
val idCol = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
while (cursor.moveToNext()) {
val id = cursor.getLong(idCol)
uris.add(ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id))
}
}
return uris
}Line-by-line walkthrough
- 1. FileProvider.getUriForFile() takes your real file path and maps it to a content:// URI using the file_paths.xml mapping — the receiving app never sees the real path.
- 2. addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) tells the system to create a temporary permission grant tied to this Intent's recipient — without this, the URI is useless.
- 3. Intent.createChooser() wraps the share intent so the system shows the app picker — each app in the picker automatically receives the URI permission.
- 4. In launchCamera, imageFile.parentFile?.mkdirs() ensures the directory exists before FileProvider tries to map the path — missing directory causes FileNotFoundException.
- 5. In NotesProvider.query(), cursor.setNotificationUri() wires the cursor to receive updates when notifyChange() is called, enabling live UI updates for registered observers.
- 6. notifyChange() after insert/update/delete propagates the change notification to all active cursors pointing at this URI — critical for consistent UI state.
- 7. ContentUris.withAppendedId() constructs a URI like content://authority/notes/42 for the newly inserted row — the standard way to return an item URI from insert().
- 8. MediaStore query uses use{} block on the cursor — this is idiomatic Kotlin that auto-closes the cursor even if an exception occurs, preventing cursor leak.
- 9. getColumnIndexOrThrow() throws if the column does not exist rather than returning -1 silently — prefer this over getColumnIndex() to catch schema mismatches early.
- 10. ContentUris.withAppendedId on MediaStore.Images.Media.EXTERNAL_CONTENT_URI builds a direct content URI for the image that Glide, Coil, or ImageDecoder can load directly.
Spot the bug
// In AndroidManifest.xml
// <provider
// android:name="androidx.core.content.FileProvider"
// android:authorities="com.example.app.provider"
// android:exported="true"
// android:grantUriPermissions="true">
// <meta-data
// android:name="android.support.FILE_PROVIDER_PATHS"
// android:resource="@xml/file_paths" />
// </provider>
fun shareDocument(context: Context, file: File) {
val uri = FileProvider.getUriForFile(
context,
"com.example.app.provider",
file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/pdf")
}
context.startActivity(intent)
}Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- ContentProvider basics — Android Developers (Android Docs)
- FileProvider — AndroidX Core (Android Docs)
- URI permissions — Android Developers (Android Docs)
- MediaStore access — scoped storage (Android Docs)
- Storage Access Framework overview (Android Docs)