Content Providers (Java)
Room and SQLiteOpenHelper both manage data inside a single app. A ContentProvider is Android's mechanism for sharing structured data between apps, behind a stable, permission-checked interface. Most apps — including RecipeBox — never need to write a custom ContentProvider unless they are deliberately exposing a reusable data source for other apps to consume. But understanding the concept matters anyway, because you will regularly consume system providers, such as the device's Contacts or the photo/video MediaStore.
The Four Methods a ContentProvider Implements
| Method | Purpose |
|---|---|
| query() | Returns a Cursor of matching rows |
| insert() | Adds a new row, returns its new Uri |
| update() | Modifies existing rows matching a selection |
| delete() | Removes rows matching a selection |
These four methods intentionally mirror the SQLite operations from earlier lessons — a ContentProvider is often just a permission-checked wrapper around a database like the one built with SQLiteOpenHelper or Room.
Anatomy of a content:// URI
- Authority — uniquely identifies which provider to talk to (usually the app's package name).
- Path — identifies which table or data type within that provider (recipes).
- Id (optional) — identifies one specific row (42).
Querying the System MediaStore
Suppose RecipeBox wants to let the user pick an existing photo from their device to attach as a recipe photo, rather than taking a new one. That means querying the system's MediaStore ContentProvider for images already on the device.
// Querying MediaStore for the device's images
Uri imagesUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME };
Cursor cursor = getContentResolver().query(
imagesUri, projection, null, null,
MediaStore.Images.Media.DATE_ADDED + " DESC"
);
if (cursor != null) {
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
Uri photoUri = ContentUris.withAppendedId(imagesUri, id);
// photoUri can now be shown in an ImageView or offered as a recipe photo choice
}
cursor.close();
}
- getContentResolver() is the entry point for talking to any ContentProvider, system or custom.
- RecipeBox never touches the Gallery app's database directly — it goes through the well-defined MediaStore contract.
- ContentUris.withAppendedId() builds the per-image URI from the table URI plus its id column.
FileProvider: Sharing a File Safely
The far more common real-world need is the opposite direction: RecipeBox wants to share one of its own private files — a recipe photo saved in app-specific storage — with another app (say, a messaging app, so the user can send a recipe photo to a friend). Handing out a raw file:// path doesn't work on modern Android and is a security risk even where it does — the other app would have direct filesystem access it has no business having. FileProvider is a special, ready-made ContentProvider (you don't write query/insert/update/delete yourself) built exactly for this: it hands out a scoped content:// URI instead.
Step 1 — declare it in AndroidManifest.xml:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.recipebox.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
Step 2 — declare which folders are shareable in res/xml/file_paths.xml:
<paths>
<external-files-path
name="recipe_photos"
path="recipe_photos/" />
</paths>
Step 3 — generate a shareable Uri and grant temporary access:
// Sharing a recipe photo file with another app
File photoFile = new File(context.getExternalFilesDir("recipe_photos"), "recipe_12.jpg");
Uri shareUri = FileProvider.getUriForFile(
context,
"com.example.recipebox.fileprovider",
photoFile
);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, shareUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share recipe photo"));
- FileProvider.getUriForFile() turns a raw File into a safe, temporary content:// URI.
- FLAG_GRANT_READ_URI_PERMISSION grants the receiving app read access to just that one file, only for as long as it needs it — not blanket access to RecipeBox's storage.
- This is the standard, modern way to share a file between apps — you will see it again with camera intents and print/export features.