Activity Lifecycle

An Activity is never simply "on" or "off" — the Android system moves it through a well-defined sequence of states as the user opens it, switches away, rotates the device, or returns to it. This sequence is the Activity lifecycle, and every screen in TrailLog — the trail list, TrailDetailActivity, a future settings screen — passes through the exact same states. Knowing what to do (and what not to do) in each one is one of the first real Android skills worth mastering.

The Full Lifecycle Sequence

onCreate() ──▶ onStart() ──▶ onResume() ──▶ [ Activity running, visible, interactive ] │ ▼ onPause() │ ▼ onStop() │ ▼ onDestroy()

The system calls onCreate() exactly once when the Activity is first built, then onStart() and onResume() as it becomes visible and interactive. When the user navigates away, the same journey happens in reverse: onPause(), onStop(), and eventually onDestroy(). An Activity can also bounce back from onStop() straight to onStart()/onResume() if the user returns to it before it's destroyed — the sequence isn't always a straight line to the end.

What Belongs in Each Callback

CallbackWhat goes here
onCreate()One-time setup: inflating the layout, binding views, reading Intent extras, setting up a ViewModel reference.
onStart()The Activity is about to become visible. Register anything the user should see reflected immediately.
onResume()The Activity is now in the foreground and interactive. Start anything that only makes sense while actively on screen — like a live location listener.
onPause()Another screen is partially covering this one. Stop anything you started in onResume() and save lightweight state.
onStop()The Activity is no longer visible at all. Release heavier resources you no longer need.
onDestroy()Final cleanup before the Activity object is discarded, whether the user is done with it or the system needs the memory back.

Worked Example: Starting and Stopping a Location Listener

TrailDetailActivity shows a trail's saved latitude and longitude, but say it also offers a "live distance from here" reading while the screen is open. It only makes sense to listen for location updates while the user can actually see that number, so the listener starts in onResume() and stops in onPause():

class TrailDetailActivity : AppCompatActivity() {

    private var isListeningForLocation = false

    override fun onResume() {
        super.onResume()
        startLocationUpdates()
        isListeningForLocation = true
    }

    override fun onPause() {
        super.onPause()
        if (isListeningForLocation) {
            stopLocationUpdates()
            isListeningForLocation = false
        }
    }
}

If you started the listener in onCreate() instead and never stopped it, it would keep draining the device's battery long after the user left the screen — a classic Android resource leak that the pause/resume pairing exists specifically to prevent.

Surviving Configuration Changes

Rotating the device (or a handful of other configuration changes) destroys and recreates the current Activity by default — a fresh onCreate() runs as if the screen had just been opened. Any plain variable holding in-progress state, like text the user was mid-way through typing into a trail's notes field, is lost unless you explicitly save it. This is a genuinely common trap for people new to Android: the app "loses" what the user just typed the instant they rotate the phone.

onSaveInstanceState() is called before the Activity is destroyed for this reason, and onCreate()/onRestoreInstanceState() receive that saved Bundle back afterward:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putString("draft_notes", notesEditText.text.toString())
}

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

    val draftNotes = savedInstanceState?.getString("draft_notes")
    if (draftNotes != null) {
        notesEditText.setText(draftNotes)
    }
}

This is only meant for small, lightweight bits of transient UI state — not for storing whole objects like a full Trail list. Persistent data belongs in Room or DataStore, both covered later in this track.

The Modern Alternative: Lifecycle-Aware Components

Overriding six callbacks by hand works, but it scatters lifecycle-dependent logic across the entire Activity class and makes it hard to reuse. Jetpack's Lifecycle library offers a cleaner alternative: implement DefaultLifecycleObserver in a small, focused class and attach it to the Activity's lifecycle instead of overriding methods on the Activity itself.

class LocationListenerObserver(private val onLocationTick: () -> Unit) : DefaultLifecycleObserver {

    override fun onResume(owner: LifecycleOwner) {
        // start listening for location updates
    }

    override fun onPause(owner: LifecycleOwner) {
        // stop listening for location updates
    }
}

You then register it once — lifecycle.addObserver(LocationListenerObserver { ... }) — and the object automatically receives the right calls at the right time, without the Activity needing to override anything itself. This same lifecycle-awareness is what makes LiveData able to stop delivering updates to a destroyed screen automatically, which you'll see in ViewModel & LiveData.

Rule of thumb: put one-time, heavier setup in onCreate(), and release anything that costs battery or memory — listeners, streams, connections — no later than onStop(). Anything left running past that point keeps working even though the user can no longer see it.