FileProvider & Content Providers

The previous lesson saved a TrailLog photo to the app's own private storage. That works fine as long as only TrailLog itself ever needs to look at the file — but the moment you want to hand that photo to a camera app, or offer it to a share sheet so the user can send a hiking photo to a friend, you're crossing an app boundary. Android has a dedicated mechanism for sharing structured data between apps, and a specific, much more common tool built on top of it for sharing files safely. This lesson covers both.

ContentProvider: Sharing Structured Data Between Apps

A ContentProvider is Android's general-purpose mechanism for exposing structured data — rows and columns, much like a database table — to other apps, through a stable content:// URI and a query-based interface (query(), insert(), update(), delete()). It's how the system's own contacts, media store, and calendar data get shared with every app that has permission to read them.

Here's the practical reality worth knowing up front: most apps never need to write a custom ContentProvider. If TrailLog just needs its own Room database for its own screens, a plain DAO (as in the earlier Room Database lesson) is all that's required — a ContentProvider only earns its complexity when you want to deliberately expose your app's data as a reusable source for other apps, the way the system's contacts provider does. That said, understanding the concept matters, because you'll regularly be on the consuming side of one — reading a photo the user picked from their device gallery, for instance, means querying a ContentProvider you didn't write, through a content:// URI you didn't create.

The Real Problem: Sharing a File Without a Raw file:// URI

Say TrailLog wants to hand a trail photo to another app — either so a camera app can save a new photo into it, or so a share sheet can send an existing photo to a messaging app. The naive approach would be to just pass a plain file:// path pointing at the photo on disk. Modern Android actively blocks this: since Android 7.0 (API 24), passing a file:// URI outside your own app in an Intent throws a FileUriExposedException. The reasoning is security — a raw file path can leak your app's private storage layout and gives the receiving app unrestricted, unrevokable filesystem access.

FileProvider is the sanctioned fix: a special, pre-built ContentProvider (part of AndroidX) that turns a file your app owns into a temporary, permission-scoped content:// URI another app can use, without ever exposing the real file path or granting broader access than you explicitly allow.

Step 1: Declaring FileProvider in the Manifest

Add a <provider> entry inside AndroidManifest.xml. It doesn't need its own code — you attach AndroidX's built-in androidx.core.content.FileProvider class and point it at a resource file describing which folders it's allowed to share:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.codingwithsonu.traillog.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

android:exported="false" keeps the provider closed to arbitrary apps — access only happens through URIs your own app explicitly generates and grants. android:authorities must be unique, so it's conventionally your app's package name plus a fixed suffix.

Step 2: Declaring Shareable Folders in file_paths.xml

Create res/xml/file_paths.xml to tell FileProvider exactly which directories it's allowed to expose. TrailLog stores photos in its app-specific external "Pictures" folder, so that's the one folder we declare:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path
        name="trail_photos"
        path="Pictures" />
</paths>

<external-files-path> matches the app-specific external storage folder from the previous lesson's getExternalFilesDir() call. The name attribute is just a label used internally to build the resulting URI's path segment — it doesn't need to match the real folder name.

Step 3: Generating a Shareable URI with getUriForFile()

With the manifest entry and file_paths.xml in place, turning a real file into a safe, shareable URI is one call:

val photoFile = File(getExternalFilesDir("Pictures"), "trail_${trail.id}.jpg")

val photoUri: Uri = FileProvider.getUriForFile(
    this,
    "com.codingwithsonu.traillog.fileprovider",
    photoFile
)
// photoUri looks like: content://com.codingwithsonu.traillog.fileprovider/trail_photos/trail_42.jpg

Notice the result is a content:// URI, not a file:// one — no real filesystem path is ever exposed to whoever receives it.

Step 4: Granting Temporary Read Permission

Generating the URI isn't enough on its own — by default, the receiving app still has no permission to open it. You have to explicitly grant read access on the Intent that carries the URI:

val shareIntent = Intent(Intent.ACTION_SEND).apply {
    type = "image/jpeg"
    putExtra(Intent.EXTRA_STREAM, photoUri)
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(shareIntent, "Share trail photo"))

This explicit grant is necessary because FileProvider's data isn't public by design — android:exported="false" means no app can just query it freely. Attaching FLAG_GRANT_READ_URI_PERMISSION to the Intent tells the system "whichever app ends up handling this specific Intent gets a temporary, revocable read grant for this one URI" — nothing more. The permission expires once the receiving activity finishes (or you can revoke it earlier with revokeUriPermission()), so you never leave a standing hole in your app's private storage.

Note: This manifest entry, file_paths.xml, and Kotlin code are all correct, real Android configuration — but this website's own project doesn't run a live Android Studio build, so nothing here executes on the page itself. Wire it into a real TrailLog project to see the share sheet appear.

Where This Exact Pattern Comes Back

This same FileProvider setup gets reused twice more, later in this track, for genuinely common real needs:

LessonHow it reuses this URI
CameraXhands the camera app a FileProvider URI as the destination to save a freshly-captured trail photo into
Sharing & Implicit Intentsshares an existing trail photo out to any installed app through a system share sheet, using the exact ACTION_SEND pattern above
Up next: So far, each lesson has TrailLog's data living in one place at a time — DataStore, Room, or a file. The Repository Pattern shows how to combine several data sources behind one clean API the rest of the app can depend on.