Activities & Intents

An Activity represents one single screen with a user interface — the list of trails, a single trail's detail view, a settings screen. Every screen the user actually looks at in a TrailLog-style app is backed by an Activity (or, as you'll see two lessons from now, a Fragment hosted inside one). Almost every Activity you write subclasses AppCompatActivity, which gives you a consistent look across old and new versions of Android plus a handful of compatibility helpers you'd otherwise have to write yourself.

A Second Screen for TrailLog: TrailDetailActivity

Right now imagine TrailLog has exactly one screen: a list of logged trails. The moment you want to tap a trail and see its full notes, photo, and GPS coordinates, you need a second screen. That second screen is a new Activity — TrailDetailActivity.

class TrailDetailActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_trail_detail)

        val trailId = intent.getIntExtra("trail_id", -1)
        // look up the Trail with this id and bind it to the views below
    }
}

TrailDetailActivity has its own layout, its own lifecycle, and its own job: show one Trail in full. It doesn't know or care how it got launched — that's the job of an Intent.

What an Intent Actually Is

An Intent is a message object that requests an action from another Android component. Think of it as an envelope: you address it (which Activity should handle this?), you can put things inside it (data to hand over), and then you send it off with a system call like startActivity(). The system reads the envelope and delivers it to the right place.

Explicit Intents: Navigating Inside Your Own App

An explicit intent names the exact component you want to start — you already know which class should handle it, because it's a screen you wrote yourself. This is the normal way to move between screens inside a single app. Here's the trail list handing off to TrailDetailActivity when the user taps a trail:

private fun openTrailDetail(trail: Trail) {
    val intent = Intent(this, TrailDetailActivity::class.java)
    startActivity(intent)
}

Intent(this, TrailDetailActivity::class.java) is the explicit form — the first argument is a Context, the second is the exact class to launch. Calling startActivity(intent) hands that envelope to the system, which creates (or resumes) TrailDetailActivity and brings it to the foreground.

Passing Data Between Activities with Extras

An empty Intent only tells the system which screen to open — it doesn't carry any data on its own. To tell TrailDetailActivity which trail to display, you attach key-value pairs called extras:

Sending the trail's id

private fun openTrailDetail(trail: Trail) {
    val intent = Intent(this, TrailDetailActivity::class.java)
    intent.putExtra("trail_id", trail.id)
    startActivity(intent)
}

Reading it back inside TrailDetailActivity

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_trail_detail)

    val trailId = intent.getIntExtra("trail_id", -1)
    if (trailId == -1) {
        finish() // no valid id was passed in, bail out
        return
    }
    // fetch the matching Trail (from a repository, in later lessons) and render it
}

The second argument to getIntExtra() is a default value used if the key is missing — a small but important safety net so a malformed launch never crashes the screen outright.

Note: These snippets are correct, real Kotlin/Android code, but this website's own project doesn't run a live Android Studio build, so nothing here executes on the page itself — copy it into a real TrailLog project to see it work.

Getting a Result Back: ActivityResultContracts

Sometimes you don't just want to open a screen — you want an answer back from it. Say TrailDetailActivity has an "Edit" mode, and after the user saves changes you want the list screen to know a trail was updated. Older Android code handled this with startActivityForResult() and onActivityResult(), but that API is deprecated — it's easy to misuse, and result codes/request codes were just loosely-typed integers you had to keep track of by hand.

The modern, type-safer replacement is ActivityResultContracts, registered once per launching component:

private val editTrailLauncher =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
            // a trail was edited — refresh the list
            reloadTrails()
        }
    }

private fun openTrailForEditing(trail: Trail) {
    val intent = Intent(this, TrailDetailActivity::class.java)
    intent.putExtra("trail_id", trail.id)
    editTrailLauncher.launch(intent)
}

This genuinely matters for a modern app: registerForActivityResult is the API Android currently recommends, and it's worth flagging clearly that anything you find online using startActivityForResult is legacy and should not be used in new code.

Implicit Intents: A Quick Preview

Explicit intents work when you know exactly which of your own Activities to launch. An implicit intent is different — instead of naming a component, you describe an action (view this, share this, dial this number) and let the Android system find whichever installed app can handle it. If TrailLog later lets users share a trail summary to any app they choose, that's an implicit intent — we cover that fully in Sharing & Implicit Intents later in the track.

Declaring an Activity in AndroidManifest.xml

Every Activity — no exceptions — must be declared in AndroidManifest.xml, or the system will refuse to start it and crash with an ActivityNotFoundException.

<application android:label="TrailLog">
    <activity android:name=".TrailListActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity android:name=".TrailDetailActivity" />
</application>

Only TrailListActivity carries the launcher intent-filter — that's what makes it the screen that opens when the user taps the TrailLog app icon. TrailDetailActivity just needs a plain declaration; it's only ever reached through an explicit intent from inside the app.

Up next: An Activity doesn't just appear and sit still — the system creates, pauses, and destroys it as the user moves around the device. Activity Lifecycle walks through exactly what happens and when.