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

ActionWhat it does
ACTION_VIEWOpens 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_DIALOpens 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_MULTIPLELike 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.

Note: This Kotlin code is correct, real implicit-intent usage, but this website's own project doesn't run a live Android Studio build, so nothing here executes on the page itself.

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.

Up next: TrailLog has been entirely local and device-bound so far. Setup & Analytics starts connecting it to the cloud with Firebase.