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.
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:
| Lesson | How it reuses this URI |
|---|---|
| CameraX | hands the camera app a FileProvider URI as the destination to save a freshly-captured trail photo into |
| Sharing & Implicit Intents | shares an existing trail photo out to any installed app through a system share sheet, using the exact ACTION_SEND pattern above |