Skip to content

Intents

Intents Deep Dive

Overview

Intents are Android's primary mechanism for inter-component communication. An Intent encapsulates the "request" to perform some action, and can be used to start Activities, Services, or deliver Broadcasts. They're fundamental to Android's loose coupling philosophy.


Intent Architecture

Intent Structure

data class Intent(
    action: String = ACTION_MAIN,
    uri: Uri? = null,
    pkg: String? = null,
    cls: ComponentName? = null,
    extras: Bundle = Bundle(),
    flags: Int = 0,
    categories: Set<String> = emptySet(),
    type: String? = null,  // MIME type
    selector: Intent? = null  // For intent forwarding
)

Core Intent Properties

Action: What operation to perform

ACTION_MAIN       - App entry point
ACTION_VIEW       - Display data
ACTION_SEND       - Send data
ACTION_CALL       - Initiate phone call
ACTION_SETTINGS   - Open system settings

Category: Hints about the component

CATEGORY_LAUNCHER    - Launchable icon
CATEGORY_DEFAULT     - Default handler
CATEGORY_BROWSABLE   - Can be invoked by browser

Data: URI and MIME type

scheme://host:port/path?query#fragment
content://contacts/people/1
https://example.com/page
file:///path/to/file

Component: Explicit target (optional for implicit)

ComponentName(packageName, className)

Extras: Bundle of additional data

intent.putExtra("key", value)
intent.putParcelableArrayListExtra("items", items)

Flags: Special handling instructions

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP


Explicit vs Implicit Intents

Explicit Intent

You specify the exact component to start

// Explicit - you know exactly what to start
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("item_id", 123)
startActivity(intent)

Characteristics: - Package name + Class name known - Guaranteed to reach target (or fail if not found) - Direct app-to-app communication - Used for internal app navigation - Fastest resolution

Internal app example:

// From MainActivity to SettingsActivity
Intent(this, SettingsActivity::class.java).apply {
    putExtra("tab", "display")
    startActivity(this)
}

Implicit Intent

You describe what you want, system finds handler

// Implicit - system decides who handles it
val intent = Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("https://example.com")
}
startActivity(intent)

// System: "Who can handle VIEW action with https URI?" โ†’ Browser

Characteristics: - Describe desired action + data - System matches against installed app intent filters - May show chooser if multiple matches - Used for cross-app communication - System decides implementation - Decouples apps

Cross-app examples:

// Open browser
Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))

// Send email
Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, arrayOf("user@example.com"))
    putExtra(Intent.EXTRA_SUBJECT, "Hello")
    putExtra(Intent.EXTRA_TEXT, "Message body")
    type = "message/rfc822"
}

// Share to any app that accepts text
Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_TEXT, "Check this out!")
    type = "text/plain"
}


Intent Filters

Declaration in Manifest

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.DEFAULT" />

        <data
            android:scheme="https"
            android:host="example.com"
            android:pathPattern="/products/.*"
            android:mimeType="application/pdf"
        />
    </intent-filter>
</activity>

Matching Algorithm

For implicit intent to match, ALL of these must pass:

  1. Action Match (exactly)
  2. Intent action must match one declared action
  3. If intent has no action: matches any filter with action

  4. Category Match (all must match)

  5. All intent categories must be in filter
  6. CATEGORY_DEFAULT added automatically if not specified
  7. Filter CATEGORY_DEFAULT is implicit requirement

  8. Data Match

  9. Intent URI + MIME type must match
  10. Both scheme AND host must match
  11. MIME type wildcard: text/*, */*

Example matching:

<!-- Filter -->
<filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="https" android:host="example.com" />
</filter>

<!-- โœ… MATCHES -->
Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("https://example.com/page")
}

<!-- โŒ DOESN'T MATCH: wrong scheme -->
Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("http://example.com/page")  // http not https
}

<!-- โŒ DOESN'T MATCH: wrong action -->
Intent(Intent.ACTION_SEND).apply {
    data = Uri.parse("https://example.com/page")
}

Intent Resolution

Resolution Process (Step-by-Step)

1. Find all installed apps with components declaring intent filters for this action/data

Query: "Which apps can handle VIEW + https://maps.example.com?"

Result:
- Google Maps (can handle location URLs)
- Google Chrome (can handle https)
- Safari (can handle https)

2. Filter by Action - Intent action must match exactly one declared action - Empty intent action matches any filter with action

3. Filter by Category - All intent categories must exist in filter - CATEGORY_DEFAULT special handling

4. Filter by Data - Scheme, host, port, path, MIME type - Must satisfy all specified constraints

5. Result - No matches โ†’ ActivityNotFoundException - One match โ†’ Start that activity - Multiple matches โ†’ Show chooser dialog

Resolution Code

// Implicit intent
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://example.com/page")

// Android internally:
val resolveInfoList = packageManager.queryIntentActivities(intent, 0)
// Returns all activities that can handle this intent

if (resolveInfoList.isEmpty()) {
    // No handler found
    throw ActivityNotFoundException()
}

if (resolveInfoList.size == 1) {
    // Only one handler, start it
    startActivity(intent)
}

if (resolveInfoList.size > 1) {
    // Multiple handlers, show chooser
    val chooser = Intent.createChooser(intent, "Open with")
    startActivity(chooser)
}

Intent Flags: Complete Reference

Task/Back Stack Flags

FLAG_ACTIVITY_NEW_TASK

startActivity(Intent(this, BrowserActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
// Starts activity in a NEW task (new root in back stack)
// Used when starting from non-Activity context (Service, Broadcast)

// Without: Exception!
// Service can't start activity without FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_SINGLE_TOP

Before:  [A] [B] [C] โ† top
Intent to C with FLAG_ACTIVITY_SINGLE_TOP

After:   [A] [B] [C] (C.onNewIntent() called instead of onCreate)
// Prevents duplicate at top of stack

FLAG_ACTIVITY_CLEAR_TOP

Before:  [A] [B] [C] [D] โ† top
Intent to B with FLAG_ACTIVITY_CLEAR_TOP

After:   [A] [B] โ† top
// C and D removed, B comes to top (or recreated if not running)

FLAG_ACTIVITY_CLEAR_TASK

Before:  TaskX: [A] [B] [C]
Intent to new activity with FLAG_ACTIVITY_CLEAR_TASK

After:   TaskX: [NewActivity] โ† top
// Previous task cleared entirely
// New activity becomes sole member of task

FLAG_ACTIVITY_TASK_ON_HOME

// Activity can be started from home screen
// Placed on home task if no existing task

Visibility/Reset Flags

FLAG_ACTIVITY_NO_HISTORY

// Activity won't appear in recents
// Won't appear in back button history
// Back button skips it

FLAG_ACTIVITY_PREVIOUS_IS_TOP

// For internal use by system
// Previous activity should be treated as top


PendingIntent

What It Is

Wrapped Intent to be executed later by another app with your permissions

// Normal Intent
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)

// PendingIntent
val pendingIntent = PendingIntent.getActivity(
    this,
    requestCode = 0,
    intent = Intent(this, MainActivity::class.java),
    flags = PendingIntent.FLAG_IMMUTABLE
)
// Can be passed to notification, alarm, widget, etc
// They execute it later

Why Needed

Problem: System components don't have access to your Activity

Notification system: "User tapped notification"
Notification: "I have a PendingIntent from earlier"
Notification: "Execute this PendingIntent"
System: Runs it as if from your app

Common Uses

1. Notifications

val pendingIntent = PendingIntent.getActivity(
    context,
    0,
    Intent(context, DetailActivity::class.java),
    PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

NotificationCompat.Builder(context)
    .setContentIntent(pendingIntent)
    .build()

2. Alarms

val pendingIntent = PendingIntent.getBroadcast(
    context,
    alarmID,
    Intent(context, AlarmReceiver::class.java),
    PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

alarmManager.setAndAllowWhileIdle(
    AlarmManager.RTC_WAKEUP,
    triggerTime,
    pendingIntent
)

3. Widget Buttons

val pendingIntent = PendingIntent.getService(
    context,
    0,
    Intent(context, MyService::class.java),
    PendingIntent.FLAG_IMMUTABLE
)

remoteViews.setOnClickPendingIntent(R.id.button, pendingIntent)

Types

// Start Activity
PendingIntent.getActivity(context, id, intent, flags)

// Start Service
PendingIntent.getService(context, id, intent, flags)

// Send Broadcast
PendingIntent.getBroadcast(context, id, intent, flags)

// Send Foreground Service Intent (Android 12+)
PendingIntent.getForegroundService(context, id, intent, flags)

Flags: Critical Security

FLAG_IMMUTABLE (Android 12+ required)

// Cannot be modified by caller
// Safer, recommended for all new code
PendingIntent.getActivity(
    context, 0, intent,
    PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

FLAG_MUTABLE (Deprecated, avoid)

// Can be modified by anyone who receives it
// Security risk
// Only use if absolutely necessary

FLAG_UPDATE_CURRENT

// Reuse existing PendingIntent if exists
// Update extras if provided

FLAG_CANCEL_CURRENT

// Cancel existing PendingIntent
// Create new one


Intent Data Extras

Passing Data

With Intent

val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("user_id", 123)
intent.putExtra("user_name", "Alice")
intent.putExtra("user", userObject)  // Must be Parcelable/Serializable

startActivity(intent)

// Receiving
val userId = intent.getIntExtra("user_id", -1)
val userName = intent.getStringExtra("user_name")
val user = intent.getParcelableExtra<UserData>("user")

Size Limitation - Bundle size ~500KB but depends on device - Large data should use shared database/file instead

Best Practice

// Instead of passing large objects directly
intent.putExtra("item_id", 123)

// In receiving activity
val itemId = intent.getIntExtra("item_id", -1)
val item = viewModel.loadItem(itemId)  // Fetch from repository


Common Interview Questions

Q: Can you pass ArrayList to Intent? A: Yes, if objects are Parcelable:

val list = arrayListOf(item1, item2)
intent.putParcelableArrayListExtra("items", list as ArrayList)

Q: What's the difference between ACTION_SEND and ACTION_SEND_MULTIPLE? A: ACTION_SEND for single item, ACTION_SEND_MULTIPLE for multiple items

Q: Why use createChooser()? A: Shows chooser even if one handler exists, allows "Always" selection

Q: When is DATA filter applied? A: Only if Intent specifies data (setData/setDataAndType)

Q: Why use FLAG_ACTIVITY_CLEAR_TOP? A: Prevents duplicates in back stack, useful for deep links


Key Takeaways

โœ… Explicit = known target, fast, internal navigation

โœ… Implicit = describe action, system decides, cross-app

โœ… Intent Filters declare what implicit intents are handled

โœ… Resolution matches Action โ†’ Categories โ†’ Data

โœ… Flags control back stack behavior

โœ… PendingIntent = deferred execution with your permissions

โœ… Always use FLAG_IMMUTABLE for PendingIntent (Android 12+)