Sharing & Implicit Intents
Back in Activities & Intents, implicit intents got a brief mention as "explicit intents' opposite" and were set aside for later. This lesson is that "later" — a full look at implicit intents, using TrailLog's most natural use for one: letting a user share a finished trail with any app they choose.
What Makes an Intent "Implicit"
An explicit intent names an exact component — you already know it's
TrailDetailActivity you want to open, because you wrote it. An
implicit intent is different: instead of naming a component, it describes an
action — "send this", "view this", "dial this number" — and lets the Android system figure
out which installed app (or apps) can actually handle it. If exactly one app can handle it, the
system may launch that one directly; if several can, the system shows the user a chooser so they
pick which one. TrailLog never needs to know or care which specific app ends up handling the
request — that decision belongs to the system and the user, not the app that sent the intent.
ACTION_SEND: Sharing a Trail's Summary and Photo
ACTION_SEND is the most common implicit intent by far — it's the action behind every
"Share" button you've ever tapped in any app. Sharing a finished TrailLog trail means attaching both
a short text summary and the trail's photo, reusing the exact FileProvider URI setup
from FileProvider & Content Providers:
fun shareTrail(context: Context, trail: Trail, photoFile: File) {
val photoUri: Uri = FileProvider.getUriForFile(
context,
"com.codingwithsonu.traillog.fileprovider",
photoFile
)
val summary = "${trail.name} — ${trail.distanceKm} km on ${trail.date}\n${trail.notes}"
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "image/jpeg"
putExtra(Intent.EXTRA_TEXT, summary)
putExtra(Intent.EXTRA_STREAM, photoUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(shareIntent, "Share trail"))
}
EXTRA_TEXT carries the trail summary, EXTRA_STREAM carries the photo's
content URI, and type = "image/jpeg" tells the system what kind of content is being
shared so it can filter to apps that actually accept images.
FLAG_GRANT_READ_URI_PERMISSION is the same temporary read grant covered in the
FileProvider lesson — required here for the exact same reason, since the receiving app
has no standing access to TrailLog's private storage.
Always Wrapping in Intent.createChooser()
Notice shareIntent is never passed directly to startActivity() — it's
wrapped in Intent.createChooser(shareIntent, "Share trail") first. Without the
chooser, if the user previously set one app as their default handler for ACTION_SEND,
Android may silently reopen that same app every time with no choice offered at all.
createChooser() forces the system share sheet to appear every time, letting the user
pick a different destination whenever they want to.
Other Common Implicit Intents
| Action | What it does |
|---|---|
ACTION_VIEW | Opens a URL, a map location, or similar content in whatever app is registered to handle it — e.g. opening a trail's coordinates in a full standalone maps app. |
ACTION_DIAL | Opens the phone's dialer pre-filled with a number, without actually placing the call — useful for something like a trailhead's emergency contact number. |
ACTION_SEND_MULTIPLE | Like ACTION_SEND, but for sharing several items at once — e.g. sharing every photo from a multi-day hike in one action, using EXTRA_STREAM with an ArrayList<Uri> instead of a single URI. |
Checking Whether Any App Can Handle It First
An implicit intent assumes some app on the device can handle the requested action — but
that isn't guaranteed. Firing ACTION_DIAL on a device with no dialer app at all, for
example, would crash with an ActivityNotFoundException if launched blindly.
resolveActivity() checks first whether the system can actually find a match:
val dialIntent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:+15551234567"))
if (dialIntent.resolveActivity(packageManager) != null) {
startActivity(dialIntent)
} else {
// no dialer app installed — show a message instead of crashing
}
This one check is what turns "an implicit intent that assumes an app exists" into code that degrades gracefully on the device where that assumption turns out to be wrong.
A Cleaner Alternative: ShareCompat.IntentBuilder
The ACTION_SEND intent built by hand above is common enough that AndroidX offers a
slightly more Kotlin-friendly builder for the exact same pattern:
val shareIntent = ShareCompat.IntentBuilder(context)
.setType("image/jpeg")
.setText(summary)
.setStream(photoUri)
.setChooserTitle("Share trail")
.createChooserIntent()
context.startActivity(shareIntent)
It produces the same underlying Intent, already wrapped in a chooser — purely a matter
of a tidier call chain over manually setting each extra.