Course outline

Android development / Basic: Android and Compose foundations / Lesson 3

Components, manifests, and intents

Understand how Android enters an application and how explicit contracts allow components and other apps to cooperate safely.

About 5 min readContent reviewed
On this page

Lesson goals

  • Distinguish activities, services, receivers, and content providers.
  • Declare component exposure and pass small validated arguments.

Choose a component by responsibility

An activity provides an entry point for user interaction and usually hosts a Compose navigation tree. A service represents work without its own screen; it is not automatically a background thread. A broadcast receiver handles a delivered event briefly, while a content provider exposes structured data through a controlled URI contract. Android can instantiate declared components without following the path through a previous screen. Therefore initialization must not depend on a specific activity having run first, and application-wide objects should have explicit, limited responsibilities.

Declare exposure deliberately

The manifest contributes installation metadata and component declarations. A launcher activity needs an intent filter matching the launcher contract and must be accessible to the launcher. Internal activities can remain unexported. For apps targeting Android 12, API 31, or higher, components with intent filters must explicitly declare android:exported as required by the platform rules. Exporting a component grants other applications a potential entry point; it does not authenticate their arguments. Inspect the merged manifest because library manifests and build variants can add declarations beyond the file you edited.

xml Declarations inside the existing application element
<activity android:name=".MainActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".DetailActivity" android:exported="false" />

Retain the generated manifest namespace declarations and application configuration. MainActivity is externally launchable; DetailActivity is internal. This example uses two activities to teach component boundaries, even though a Compose app commonly uses one activity.

Pass identifiers instead of object graphs

An explicit intent names its destination component. An implicit intent describes an action and data and lets the system resolve a handler. In either case, treat received extras as untrusted input and keep them small. Passing a database id lets the destination reload current data and handle deletion; passing a large object creates stale copies and may exceed transaction limits. Use constants for extra names and validate required values before rendering. A missing record is a normal state the destination must present clearly, not an excuse to crash.

kotlin A small explicit navigation contract
const val EXTRA_ITEM_ID = "item_id"

fun Activity.openItem(id: String) {
    require(id.isNotBlank())
    startActivity(Intent(this, DetailActivity::class.java)
        .putExtra(EXTRA_ITEM_ID, id))
}

fun Intent.readItemId(): String? =
    getStringExtra(EXTRA_ITEM_ID)?.takeIf { it.isNotBlank() }

Place this in the same application as the declared DetailActivity and import Activity and Intent. The receiving activity must handle a null id with an error or safe exit, then query its data source. These helpers do not implement that screen.

Use platform contracts for external actions

For document selection or a permission prompt, prefer an Activity Result contract registered with lifecycle-aware APIs rather than manually managing request codes. Registration must be stable across recreation, and the callback must tolerate cancellation. A returned URI is a reference with permissions and lifetime rules, not necessarily a filesystem path. Handle the case where no external app can satisfy an implicit action. When sharing private files, use a content URI with narrowly scoped grants instead of exposing internal paths or assuming every receiver deserves permanent access.

Match context to ownership

Context gives access to resources and system services, but different contexts have different lifetimes and UI capabilities. A long-lived repository should not retain an activity simply to reach a database; use an appropriate application context for that infrastructure. UI operations still need a suitable UI owner and must obey background launch restrictions. Keep receivers short and hand durable work to an appropriate scheduler. Drawing these ownership boundaries early prevents memory leaks and avoids mistaking an available object reference for permission to perform a platform action at any time.

Common errors and fixes

Installation fails after adding an intent filter

Why it happens
A required exported declaration was omitted for the target configuration.
How to fix it
Set exported explicitly according to whether external callers need access.
Prevent it next time
Inspect the merged manifest and test installation after target changes.

Detail screen crashes on a missing extra

Why it happens
The destination assumes every caller followed the internal helper.
How to fix it
Validate the id and render a recoverable missing-input state.
Prevent it next time
Test direct entry with absent and malformed arguments.

A service freezes the UI

Why it happens
Service callbacks perform blocking work on the main thread.
How to fix it
Move blocking work to suitable asynchronous infrastructure.
Prevent it next time
Review threading separately from component selection.

Check your understanding

1. A private editor activity receives ids from your own app. Decide its exported value and its argument policy.

Show worked solution

Use exported=false when external access is unnecessary. Still validate the id because internal programming errors and restored state can provide bad input. Load the record and handle not-found independently. Exposure control reduces the entry surface; it does not remove the need for a correct destination contract.

2. A developer wants to send an entire image as an intent extra to another activity. Propose a safer contract.

Show worked solution

Store or retain the image through the appropriate data source and pass a small id or content URI. Validate access at the destination and grant only the permissions required by the recipient. This avoids oversized transactions and lets the app reason explicitly about ownership, cleanup, and access duration.

Key takeaways

  • Components are system entry points with independent lifecycles.
  • Manifest exposure and input validation solve different problems.
  • Prefer small contracts and lifecycle-aware result handling.

Official references