Intents, filters, results, and secure messaging
Intent is a request description
An Intent can identify a target component, action, data URI/type, categories, flags, and small extras.
Explicit Intent
val intent = Intent(this, CheckoutActivity::class.java)
.putExtra("orderId", "ORD-42")
startActivity(intent)
Your app names the exact component. Use this for known internal targets.
Implicit Intent
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=Chennai"))
startActivity(intent)
Android resolves capable external Activities from installed intent filters. Handle the case where no handler exists and do not put secrets into broadly resolvable requests.
Intent-filter matching
Filters compare action, categories, and data rules—not your extras. A receiving component must still validate the URI and extras.
<activity
android:name=".OrderLinkActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="shop.example.com"
android:pathPrefix="/orders/" />
</intent-filter>
</activity>
Matching https://shop.example.com/orders/... means Android may route the link here. It does not authorize whoever taps it to see any order.
Deep-link validation and authorization
val orderId = intent.data
?.pathSegments
?.takeIf { it.size == 2 && it[0] == "orders" }
?.get(1)
?.takeIf { it.matches(Regex("ORD-[0-9]+")) }
?: return finish()
viewModel.openAuthorizedOrder(orderId)
Validation checks shape. Authentication identifies the user. Authorization checks access to this order. All are required.
Activity Result APIs
Register launchers during initialization so Android can restore callback routing after recreation.
private val pickDocument = registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri: Uri? ->
if (uri != null) viewModel.importDocument(uri)
}
fun onImportClicked() {
pickDocument.launch(arrayOf("application/pdf"))
}
The callback can arrive after configuration recreation. Keep the operation’s business state in ViewModel/repository, not in a local variable captured from one old view.
URI permission
A content:// URI identifies data managed by a provider. For longer-lived access after document selection, request/persist URI permission only when the contract permits it. Do not convert arbitrary URIs to filesystem paths.
Extras are bounded transport, not a database
❌ Avoid
intent.putParcelableArrayListExtra("orders", ArrayList(allOrders))
✅ Prefer
intent.putExtra("customerId", customerId)
The destination reloads current data and survives process death without stale/oversized transactions.
Intent redirection
An exported component must not blindly forward an untrusted nested Intent with its original privileges.
val nested = intent.getParcelableExtra<Intent>("next")
startActivity(nested) // Unsafe redirection possibility.
Use allowlisted destinations/actions/data and construct a fresh explicit Intent from validated values.
Service safety
Use explicit Intents for Services. An implicit service request can be intercepted or resolved unexpectedly; modern Android also rejects important implicit binding cases.
An explicit Intent names a component; an implicit Intent describes an action/data that Android resolves against filters. Filters control routing, not business authorization. I validate every external URI/extra, use verified App Links when appropriate, pass small stable IDs, use Activity Result contracts registered early, handle process recreation, and never blindly forward an untrusted nested Intent.
Exercises
- Threat-model a password-reset deep link.
- Why do extras not participate in intent-filter resolution?
- Design document selection that still works after Activity recreation.