Activities & Lifecycle

Android Development Fundamentals
Course 1 ยท Chapter 2 ยท Activities & Lifecycle

๐Ÿ”„ Activities & Lifecycle

An Activity is a single screen in an Android app โ€” and unlike a browser tab, which the OS mostly leaves alone, an Activity can be paused, backgrounded, and even destroyed and recreated by the system at almost any time, for reasons entirely outside your app's control. This chapter covers the Activity class, its lifecycle callbacks, why the lifecycle matters in practice, and Logcat โ€” the tool used to actually watch it happen.

๐Ÿ–ผ๏ธ What an Activity Is

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }

An Activity represents one screen with a UI โ€” MainActivity is the one launched when the app icon is tapped, declared as such in AndroidManifest.xml (Chapter 1) via an intent filter. setContentView(R.layout.activity_main) is the line that actually connects this Kotlin class to the XML layout file it displays โ€” without it, the Activity exists but shows nothing.

โš  super.onCreate() Must Come First

super.onCreate(savedInstanceState) has to be the first line of an overridden onCreate โ€” it runs the base Android framework setup this Activity depends on. Skipping it, or calling it late, causes a crash. This applies to every lifecycle callback covered in this chapter, not just onCreate.

๐Ÿ” The Lifecycle Callbacks

An Activity moves through a defined sequence of states as the user (or the system) interacts with it, and Android calls a specific method at each transition:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.d("MainActivity", "onCreate called") } override fun onStart() { super.onStart() Log.d("MainActivity", "onStart called") } override fun onResume() { super.onResume() Log.d("MainActivity", "onResume called") } override fun onPause() { super.onPause() Log.d("MainActivity", "onPause called") } override fun onStop() { super.onStop() Log.d("MainActivity", "onStop called") } override fun onDestroy() { super.onDestroy() Log.d("MainActivity", "onDestroy called") } }

Each Callback, Plainly

CallbackFires when...
onCreateThe Activity is first created โ€” one-time setup (setContentView, initializing views)
onStartThe Activity becomes visible (but not yet interactive)
onResumeThe Activity is now in the foreground and interactive โ€” the user can tap things
onPauseLosing focus, but still (partially) visible โ€” e.g. a dialog appears over it
onStopNo longer visible at all โ€” e.g. user pressed Home, or navigated to another Activity
onDestroyBeing permanently removed โ€” user pressed Back, or the system reclaimed memory

Rotating the device, pressing Home, receiving a phone call mid-app, or the system simply needing memory back can all trigger these transitions โ€” often onPause/onStop followed later by onDestroy with no user action that looks like "closing the app" at all from their perspective.

vs the Web: No "Onload/Onunload" Equivalent Guarantee

Browser PageAndroid Activity
Typical lifetimeLoads once, stays alive until tab closesCreated/destroyed repeatedly during normal use
Rotation/resizeCSS reflow only โ€” JS state untouchedCan fully recreate the Activity from scratch by default
BackgroundingTab keeps running (usually)onStop then likely onDestroy if memory is needed

This is the single biggest mental shift coming from web development: a browser tab's JavaScript state is a comparatively stable, long-lived thing. An Activity's Kotlin state is not โ€” by default, rotating the screen destroys and recreates the entire Activity, which is exactly why the next section matters.

๐Ÿ’พ savedInstanceState โ€” Surviving Recreation

Before destroying an Activity for a recoverable reason (like rotation, not like the user pressing Back), Android calls onSaveInstanceState, giving a chance to stash small bits of UI state into a Bundle โ€” which then comes back as the savedInstanceState parameter in the next onCreate:

class MainActivity : AppCompatActivity() { private var counter = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Restore, if a previous instance saved something counter = savedInstanceState?.getInt("counter") ?: 0 } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putInt("counter", counter) } }

This is deliberately for small, transient UI state (a scroll position, a form field's current text, a counter like above) โ€” not a substitute for real persistence. Anything that needs to survive the app being fully closed and relaunched later belongs in a database or file (covered in a later course), not a Bundle.

โš  savedInstanceState Is Null on a Genuinely Fresh Start

The savedInstanceState?.getInt(...) null-safe call above matters: savedInstanceState is null when the Activity is starting completely fresh (first launch, or after the user explicitly closed it) โ€” the safe-call plus Elvis operator pattern from Kotlin Fundamentals Chapter 3 is exactly what handles both cases correctly in one line.

๐Ÿ“‹ Logcat โ€” Watching It All Happen

Log.d(...) (and its siblings Log.i, Log.w, Log.e) writes to Logcat โ€” Android's equivalent of console.log, viewable in Android Studio's Logcat panel (Chapter 1) while the app runs:

Log.d("MainActivity", "onCreate called") // Debug โ€” general development info Log.i("MainActivity", "User logged in") // Info โ€” notable events Log.w("MainActivity", "Cache miss") // Warn โ€” recoverable problems Log.e("MainActivity", "Failed to load data") // Error โ€” something went wrong

The first argument is a tag โ€” conventionally the class name โ€” which Logcat's search bar can filter by, letting a specific Activity's output be isolated out of the constant stream of system-level log noise every Android app produces. Running the lifecycle example above and rotating the emulator is genuinely the best way to internalize the callback order โ€” watching onPause โ†’ onStop โ†’ onDestroy โ†’ onCreate โ†’ onStart โ†’ onResume scroll past in Logcat makes the abstract sequence concrete.

๐Ÿ’ป Coding Challenges

Challenge 1: Logging Every Lifecycle Callback

Add the six lifecycle overrides from this chapter (onCreate through onDestroy) to MainActivity, each with a Log.d call using a consistent tag. Run the app, watch Logcat, then rotate the emulator (Ctrl+F11/F12, or the rotate button in the emulator toolbar) and observe which callbacks fire in what order.

Goal: See the lifecycle sequence happen for real, not just read about it.

โ†’ Solution

Challenge 2: Surviving Rotation with savedInstanceState

Add a private var clickCount: Int = 0 to MainActivity, a Button in the layout that increments and logs it on click, and the onSaveInstanceState/onCreate pair needed to restore clickCount after rotation. Click the button several times, rotate the device, and confirm (via Logcat) the count survived.

Goal: Practice the actual save/restore round trip, not just writing the two methods in isolation.

โ†’ Solution

Challenge 3: Reading a Real Logcat Filter

With the app from Challenge 1 running, use Logcat's search/filter bar to show only logs with your chosen tag, then separately filter to show only "Warn" level and above. Write a short comment describing what each filter changed about the visible output.

Goal: Get comfortable filtering Logcat's noisy default output down to what's actually relevant.

โ†’ Solution

๐Ÿ’ก Getting Comfortable Here Prevents Real Bugs

"Why did my data disappear when I rotated the phone?" is one of the most common early Android bugs, and it's a direct consequence of not internalizing this chapter. The lifecycle isn't an edge case to occasionally worry about โ€” it's the normal, constant background rhythm every Activity lives inside.

๐ŸŽฏ What's Next

Next chapter: XML Layouts & Views โ€” LinearLayout/ConstraintLayout, common Views, and view binding.