Do You Still Need a ContentProvider?
ContentProvider feels like a 2012 API — and for most internal app data, it is. Room replaced SQLite + ContentProvider for your own database. DataStore replaced SharedPreferences + ContentProvider wrappers. ViewModels and repositories replaced the CursorLoader dance. But ContentProvider hasn't gone away; it's just moved to the edges: FileProvider for sharing files, exposing data to other apps that query it, and widget/search integration. The question isn't "should I use ContentProvider?" — it's "am I sharing data across app boundaries, or within my own process?"
When you still need ContentProvider
| Use case | Provider type | Alternative? |
|---|---|---|
| Share file with camera/gallery/share sheet | FileProvider | None — required |
| Expose app data to other apps (contacts-like) | Custom ContentProvider | Bound Service + AIDL |
| App widget data binding | Custom or shared DB | Glance + in-process data |
| Search suggestions | Custom ContentProvider | In-app search only: skip |
| Cross-process within your app | ContentProvider | Room + multi-process mode (avoid) |
| SDK auto-init via provider | Library's provider | App Startup library |
If your data stays within your app, you don't need a ContentProvider. Full stop.
FileProvider: the one you probably need
Every app that shares files uses FileProvider:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.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 -->
<paths>
<cache-path name="images" path="images/" />
<files-path name="docs" path="documents/" />
</paths>
Share a file:
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "image/jpeg"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "Share"))
Without FileProvider, file:// URIs trigger a FileUriExposedException on API 24+.
Custom ContentProvider for inter-app data
If other apps need to query your data (like a note-taking app exposing notes to a launcher search):
class NotesProvider : ContentProvider() {
override fun onCreate(): Boolean = true
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
val match = uriMatcher.match(uri)
return when (match) {
NOTES -> database.query("notes", projection, selection, selectionArgs, null, null, sortOrder)
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
}
// insert, update, delete...
}
Declare with explicit permissions:
<provider
android:name=".NotesProvider"
android:authorities="com.example.app.notes"
android:exported="true"
android:readPermission="com.example.app.permission.READ_NOTES" />
android:exported="true" is required for inter-app access — and it's a security surface. Validate every query, limit exposed columns, and use permissions.
What replaced ContentProvider internally
Room + Repository for database access:
@Dao
interface NoteDao {
@Query("SELECT * FROM notes")
fun getAll(): Flow<List<Note>>
}
No Cursor, no ContentResolver, no URI matching. Type-safe, coroutine-friendly, testable.
DataStore for preferences — replaces the old SharedPreferences ContentProvider pattern some libraries used.
SharedFlow/StateFlow for in-app event broadcasting — replaces ContentObserver for your own data changes.
The ContentObserver trap
Don't use ContentObserver to watch your own app's ContentProvider. Use Room's Flow or DataStore's data Flow:
// Old: ContentObserver on your own provider
// New:
noteDao.getAll().collect { notes -> updateUI(notes) }
ContentObserver makes sense when watching another app's provider (e.g., contacts changes). For your own data, reactive queries are simpler and more reliable.
Library ContentProviders
Many SDKs init via ContentProvider (Firebase, WorkManager, etc.). This runs before Application.onCreate on the main thread. Audit your merged manifest:
./gradlew :app:processDebugManifest
grep -i "provider" app/build/intermediates/merged_manifests/debug/AndroidManifest.xml
Migrate to App Startup where the library supports it. Disable auto-init for SDKs that don't:
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" tools:replace="android:value" />
Security checklist for exported providers
-
android:exportedisfalseunless inter-app access is required - Read/write permissions declared and enforced
- URI matcher validates all paths — no path traversal
- SQL selection uses parameterized queries, not string concatenation
- Only expose minimum necessary columns
- FileProvider paths are as narrow as possible
Cross-app data sharing in 2026
Prefer explicit APIs over exported providers:
| Need | Modern approach | Legacy |
|---|---|---|
| Share file | FileProvider + FLAG_GRANT_READ_URI_PERMISSION | Exported provider |
| Share structured data | App Links + REST/GraphQL | ContentProvider query |
| Contacts integration | ContactsContract (system) | Custom provider |
| Widget data | Room + Glance remote views | Provider query |
Export providers only when Android framework requires it (sync adapters) or documented inter-app contract exists.
Room as provider replacement
Internal data access never needs ContentProvider:
@Dao
interface NoteDao {
@Query("SELECT * FROM notes WHERE id = :id")
fun observeNote(id: Long): Flow<Note?>
@Query("SELECT * FROM notes")
fun observeAll(): Flow<List<Note>>
}
Expose data to other apps via authenticated API if needed — not SQLite through ContentProvider.
Testing providers
If you maintain exported providers:
@RunWith(AndroidJUnit4::class)
class NoteProviderTest {
@Test
fun query_returnsExpectedColumns() {
val cursor = contentResolver.query(NotesContract.CONTENT_URI, null, null, null, null)
assertNotNull(cursor)
assertTrue(NotesContract.ALL_COLUMNS.all { col -> cursor.getColumnIndex(col) >= 0 })
cursor.close()
}
}
Test permission enforcement — unprivileged context should get SecurityException on write.
Pair with Android DataStore migration for key-value data that never needed providers.
Common production mistakes
Teams get content provider modern wrong in predictable ways:
- Skipping failure-mode rehearsal — run a game day or fault injection exercise before peak traffic, not after the first outage.
- Missing correlation context — every error path should carry request, trace, or tenant identifiers so incidents are debuggable.
- Optimizing for demo, not steady state — load tests, cache warm-up, and cold-start paths matter more than local dev latency.
- Undocumented trade-offs — if you chose speed over strict correctness (or vice versa), write that down for the next engineer.
Shipping content provider modern on Android fails quietly when you test only on flagship devices, skip process-death scenarios, or assume minSdk behavior matches latest API docs. Emulator-only validation misses OEM-specific battery optimizations and background execution limits.
Resources
- ContentProvider overview (Android)
- FileProvider guide
- Room persistence library
- DataStore guide
- Migrating SharedPreferences to DataStore
Frequently asked questions
Are ContentProviders still used in modern Android apps?
Yes, but mostly for specific cases: sharing files via FileProvider, exposing data to other apps (contacts, calendar integrations), SearchManager integration, and widget data binding. For sharing data within your own app, Room, DataStore, and in-process repositories have replaced ContentProvider as the default pattern.
What is FileProvider and why do I need it?
FileProvider is a ContentProvider subclass that generates content:// URIs for files, enabling secure file sharing between apps via Intent. Required when passing files to camera apps, share sheets, or other apps via Intent — file:// URIs are blocked on modern Android. You almost certainly need FileProvider even if you don't need a custom ContentProvider.
What are alternatives to ContentProvider for inter-app data sharing?
For file sharing: FileProvider. For structured data: expose a bound Service with AIDL or share via deep links to your app's UI. For simple key-value sharing: create a custom URI scheme handled by your Activity. ContentProvider remains the standard when multiple apps need query-level access to your data (like a contacts app exposing contacts).
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →