Techbase Academy
Android · Lesson 23

The Activity Lifecycle

11 phasesLifecycle-Safe Habit TrackerAndroid Studio
Phase 1 of 11
Lesson Introduction

Welcome to the Intermediate level. Up to now your apps have been small and forgiving. From here on you build apps that behave like the real ones on a phone: they survive a screen rotation, they cope when the user switches to WhatsApp and comes back, and they do not lose a half-typed message because the system needed memory.

All of that depends on one idea: the Activity lifecycle. In this lesson you will learn what happens between the moment the user taps your icon and the moment the system destroys your screen, and you will prove it by building Dessert Clicker, an app that counts how many desserts you have sold and shows a different dessert as sales grow.

This lesson covers the first two items of Unit 4 of Google's official Android Basics with Compose course: Welcome to Unit 4 and Stages of the Activity lifecycle.

By the end you will be able to:

  • Name the lifecycle callbacks of an Activity and say when each runs.
  • Log lifecycle events with Log.d and read them in Logcat.
  • Explain what a configuration change is and why it restarts your Activity.
  • Use rememberSaveable to keep state through rotation.
  • Explain process death and how it differs from rotation.
  • Describe how lifecycle-aware code (collecting data only while the screen is visible) saves battery and data.

Big idea: your app does not control when it starts, pauses or dies. The system does. Your job is to respond to these events correctly and to keep important state somewhere safe.

✏️ Your Task
Practise what you just learned about Lesson Introduction. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 2 of 11
Prerequisite Concepts
  • State and recomposition (lessons 17 and 18). A composable that reads state redraws when the state changes. remember keeps a value across recompositions but not across the Activity being destroyed.
  • MainActivity and setContent (lesson 15). The Activity is the Android component that hosts your Compose UI.
  • Logging. Log.d("TAG", "message") writes a debug line you can read in the Logcat window. Android Studio filters it by tag, package and level. You will learn the Logcat window properly in lesson 43, but you only need to type a filter today.
  • Kotlin lambdas (lesson 12). Compose callbacks are lambdas.
✏️ Your Task
Practise what you just learned about Prerequisite Concepts. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 3 of 11
Conceptual Understanding: The Life of a Screen

An Activity is one screen of your app that the system can create, show, hide and destroy. Because phones have little memory and users jump between apps all the time, the system moves your Activity through a set of states. Each move triggers a callback, a function you can override to react.

Think of a shop assistant in a busy market stall. When a customer arrives, the stall is opened (onCreate). The assistant stands ready (onStart), then actively serves (onResume). If another customer walks in front, the assistant pauses (onPause), steps back out of view (onStop) and might eventually be sent home if space is needed (onDestroy).

text
          app launched
               |
          [ onCreate ]      Activity is created. Build UI, set content.
               |
          [ onStart ]       Visible, but the user cannot interact yet.
               |
          [ onResume ]      In the foreground. Interactive. "Resumed".
               |
   another app / dialog covers part of the screen
               |
          [ onPause ]       Partly visible, not focused.
               |
   completely hidden (user pressed Home, opened another app)
               |
          [ onStop ]        Not visible. Release what you do not need.
               |
        +------+-------------------------+
        |                                |
   user returns                    finish() / system kills
        |                                |
   [ onRestart ]                    [ onDestroy ]
        |                                
   back to onStart

The callbacks at a glance

Callback When it runs Typical use
onCreate Activity is first created Call setContent, set up one-time things
onStart Activity becomes visible Start things that only matter while visible
onResume Activity gets focus Resume animations, camera, sensors
onPause Activity loses focus (may still be visible) Pause what must not continue; keep it fast
onStop Activity is no longer visible Release heavy resources, save lightweight data
onRestart Coming back from stopped Rarely overridden
onDestroy Activity is finishing or being destroyed Final cleanup

Two helpful pairs to remember:

  • onCreate / onDestroy bracket the entire life of the Activity.
  • onStart / onStop bracket the time it is visible.
  • onResume / onPause bracket the time it is in the foreground and interactive.

On a modern phone with multi-window mode (two apps side by side), an Activity can be visible but not focused, which is exactly why onStart and onResume are separate.

✏️ Your Task
Practise what you just learned about Conceptual Understanding: The Life of a Screen. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 4 of 11
Step-by-Step Build 1: Log Every Lifecycle Event

You will not memorise the diagram by reading it. You learn it by watching it happen. Create a new Empty Activity project called Dessert Clicker (package com.example.dessertclicker).

Open MainActivity.kt and replace its contents with the following. It overrides every callback and logs one line for each.

MainActivity.kt
package com.example.dessertclicker
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text
private const val TAG = "MainActivity"
class MainActivity : ComponentActivity() {
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 Log.d(TAG, "onCreate called")
 setContent {
 Text("Dessert Clicker")
 }
 }
 override fun onStart() {
 super.onStart()
 Log.d(TAG, "onStart called")
 }
 override fun onResume() {
 super.onResume()
 Log.d(TAG, "onResume called")
 }
 override fun onPause() {
 super.onPause()
 Log.d(TAG, "onPause called")
 }
 override fun onStop() {
 super.onStop()
 Log.d(TAG, "onStop called")
 }
 override fun onRestart() {
 super.onRestart()
 Log.d(TAG, "onRestart called")
 }
 override fun onDestroy() {
 super.onDestroy()
 Log.d(TAG, "onDestroy called")
 }
}

Every override calls super first. That is mandatory: the parent class does essential work and Android throws an exception if you forget.

Reading the Logcat

  1. Run the app on an emulator or a phone.
  2. Open View > Tool Windows > Logcat (it is usually a tab at the bottom).
  3. In the filter box type tag:MainActivity (the exact query syntax changes between Android Studio versions, but typing the tag usually works).

Experiments

Perform each action and write down what you see:

  1. Launch the app. You should see onCreate, onStart, onResume in that order.
  2. Press Home. You should see onPause then onStop. The app is not destroyed; it sits in the background.
  3. Reopen it from recents. You should see onRestart, onStart, onResume. Note there is no onCreate: the same Activity instance came back.
  4. Press Back on the main screen. You should see onPause, onStop, onDestroy. The Activity is finished.
  5. Rotate the device (on the emulator use the rotate buttons in the side panel). You will see the full tear-down and a fresh start: onPause, onStop, onDestroy, then onCreate, onStart, onResume.

That fifth result surprises most beginners. Rotating the phone destroys and recreates your Activity. We will fix the consequences shortly.

Experiment: pull down the notification shade without covering the app completely. On many versions you will see only onPause but no onStop, because the app is still partly visible. Compare that to opening a full-screen dialog.

✏️ Your Task
Practise what you just learned about Step-by-Step Build 1: Log Every Lifecycle Event. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 5 of 11
Step-by-Step Build 2: The Dessert Clicker Screen

The official course gives you the starting art and layout. We will build our own simple version with text only, so you do not need any downloads.

The rules of the app:

  • A big button shows the current dessert's name.
  • Each tap sells one dessert and earns the dessert's price.
  • As the number sold passes certain thresholds, the dessert changes.

The data

Create Dessert.kt:

Dessert.kt
package com.example.dessertclicker
data class Dessert(
 val name: String,
 val price: Int,
 val startProductionAmount: Int
)
val desserts = listOf(
 Dessert("Cupcake", price = 5, startProductionAmount = 0),
 Dessert("Donut", price = 10, startProductionAmount = 5),
 Dessert("Eclair", price = 15, startProductionAmount = 20),
 Dessert("Froyo", price = 30, startProductionAmount = 50),
 Dessert("Gingerbread", price = 50, startProductionAmount = 100)
)
fun determineDessertToShow(dessertsSold: Int): Dessert {
 var dessertToShow = desserts.first()
 for (dessert in desserts) {
 if (dessertsSold >= dessert.startProductionAmount) {
 dessertToShow = dessert
 } else {
 break
 }
 }
 return dessertToShow
}

determineDessertToShow walks the list; the last dessert whose startProductionAmount has been reached wins. With dessertsSold = 7 the answer is Donut. With dessertsSold = 120 it is Gingerbread.

The screen

Now write the composable. It keeps two numbers in state:

kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun DessertClickerScreen(modifier: Modifier = Modifier) {
 var revenue by remember { mutableIntStateOf(0) }
 var dessertsSold by remember { mutableIntStateOf(0) }
 val currentDessert = determineDessertToShow(dessertsSold)
 Column(
 modifier = modifier
 .fillMaxSize()
 .padding(24.dp),
 verticalArrangement = Arrangement.Center,
 horizontalAlignment = Alignment.CenterHorizontally
 ) {
 Button(
 onClick = {
 revenue += currentDessert.price
 dessertsSold++
 }
 ) {
 Text(
 text = currentDessert.name,
 style = MaterialTheme.typography.headlineMedium
 )
 }
 Text(
 text = "Desserts sold: $dessertsSold",
 modifier = Modifier.padding(top = 24.dp),
 style = MaterialTheme.typography.titleLarge
 )
 Text(
 text = "Total revenue: ₦$revenue",
 style = MaterialTheme.typography.titleLarge
 )
 }
}

Call it in setContent, inside your theme and a Scaffold as in lesson 21:

kotlin
setContent {
 DessertClickerTheme {
 Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
 DessertClickerScreen(modifier = Modifier.padding(innerPadding))
 }
 }
}

(Use your own theme name if you named the project differently, and add the Scaffold imports from lesson 21.)

Run it. Tap the button seven times. The button should read Donut and the counter 7.

Now rotate the phone. Everything resets to zero. Revenue and sales are gone, and the button says Cupcake again.

✏️ Your Task
Practise what you just learned about Step-by-Step Build 2: The Dessert Clicker Screen. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 6 of 11
The Problem: Configuration Changes

A configuration change is something about the device or environment that changes while your app is running, such as:

  • screen rotation
  • switching between light and dark theme
  • changing the system language or font size
  • plugging in a keyboard
  • changing the window size in split-screen or on a foldable

By default, Android destroys and recreates the Activity so it can reload resources suited to the new configuration, for example a different layout for landscape. All your remembered values disappear with the old Activity. This default exists for good reasons, but it hurts if you do not plan for it.

Why does remember fail? remember stores a value in the composition, and the composition belongs to the Activity instance. A new Activity instance means a new, empty composition.

The fix for simple values: rememberSaveable

rememberSaveable works like remember, but it also stores the value in the saved instance state, a small Bundle that Android keeps for you across recreation. Change the two lines:

kotlin
import androidx.compose.runtime.saveable.rememberSaveable
var revenue by rememberSaveable { mutableIntStateOf(0) }
var dessertsSold by rememberSaveable { mutableIntStateOf(0) }

Run, tap seven times, rotate. The counters now survive. The Activity still goes through onDestroy and onCreate, but the numbers are restored.

What can rememberSaveable store?

Anything that can go into a Bundle: numbers, strings, booleans, lists of those, and classes marked Parcelable or Serializable. For other types you write a custom saver (lesson 54). mutableIntStateOf is handled automatically.

Because a Bundle is held by the system, keep it small. The limit is around 1 MB for all saved state across the whole app and a transaction that is too large throws TransactionTooLargeException. Never put a bitmap or a 5,000-item list in saved state. Save an ID or a search query and reload the rest.

✏️ Your Task
Practise what you just learned about The Problem: Configuration Changes. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 7 of 11
Process Death: The Other Way to Lose State

Rotation is not the only danger. Suppose the user opens Dessert Clicker, taps a few times, then switches to Facebook, YouTube and a game. The system is low on memory, so it kills your whole app process while it is in the background. Your Activity gets no onDestroy call in that case, because the process is simply removed.

When the user returns, Android recreates the Activity and hands back the saved instance state, so values saved with rememberSaveable return, but anything held only in remember or in ordinary variables is gone.

Testing process death

You can simulate it without waiting for low memory:

  1. Run the app and tap a few times.
  2. Press Home.
  3. In Android Studio's Logcat or Running Devices toolbar, use Terminate Application (the red square), or run this in the terminal:
bash
adb shell am kill com.example.dessertclicker
  1. Reopen the app from the launcher or recents.

adb shell am kill only kills a background process, which is what makes it a realistic simulation. If the app is in the foreground it does nothing.

The summary:

Event Activity destroyed? remember survives? rememberSaveable survives?
Recomposition No Yes Yes
Rotation (configuration change) Yes No Yes
Process death Process killed No Yes
User presses Back / swipes the app away Yes, finished No No (intentional)

The last row matters: saved state is for system-initiated recreation. If the user deliberately closes the app, they expect a fresh start.

✏️ Your Task
Practise what you just learned about Process Death: The Other Way to Lose State. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 8 of 11
Lifecycle-Aware Work

Some things should only happen while the screen is visible: listening to location updates, refreshing a live score, playing a video. If you start them in onCreate and forget to stop them, they keep draining battery and mobile data in the background.

You will use two tools for this throughout the course:

  1. Lifecycle callbacks. Start in onStart, stop in onStop.
  2. Lifecycle-aware components. Objects that watch the lifecycle for you.

In Compose, the simplest lifecycle-aware tool for data streams is collectAsStateWithLifecycle(), which collects a Kotlin Flow only while the screen is at least started:

kotlin
import androidx.lifecycle.compose.collectAsStateWithLifecycle
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

You will use it in lessons 24 and 25. It stops collecting when the app goes to the background (below the STARTED state) and resumes when it returns. Plain collectAsState() keeps collecting even when the app is hidden. For streams that touch the network, database or sensors, prefer the lifecycle-aware version.

For imperative code you can observe the lifecycle from a composable with LifecycleEventEffect, which runs a block when a given lifecycle event occurs:

kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import android.util.Log
@Composable
fun LifecycleLogger() {
 LifecycleEventEffect(Lifecycle.Event.ON_START) {
 Log.d("LifecycleLogger", "Screen became visible")
 }
 LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
 Log.d("LifecycleLogger", "Screen is hidden")
 }
}

This needs the androidx.lifecycle:lifecycle-runtime-compose dependency, which the Empty Activity template normally already includes through version-catalog entries. If Android Studio cannot resolve the import, add it (look up the latest stable version rather than copying a number from a tutorial, and see lesson 28 for how to add dependencies).

✏️ Your Task
Practise what you just learned about Lifecycle-Aware Work. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 9 of 11
Guided Practice Exercises
🎯 Your Challenge

Exercise 1: Log the whole story

Add Log.d calls to DessertClickerScreen so that you can see, in Logcat:

  1. a line when the screen first enters composition (Composable entered composition);
  2. a line when it leaves composition (Composable left composition);
  3. a line every time the button is tapped, showing dessertsSold.

Then rotate the phone and describe the order of the Activity and composable log lines.

Hint: use DisposableEffect for entering and leaving.

kotlin
import android.util.Log
import androidx.compose.runtime.DisposableEffect
private const val TAG = "DessertClicker"
@Composable
fun DessertClickerScreen(modifier: Modifier = Modifier) {
 var revenue by rememberSaveable { mutableIntStateOf(0) }
 var dessertsSold by rememberSaveable { mutableIntStateOf(0) }
 DisposableEffect(Unit) {
 Log.d(TAG, "Composable entered composition")
 onDispose {
 Log.d(TAG, "Composable left composition")
 }
 }
 val currentDessert = determineDessertToShow(dessertsSold)
 Column(
 modifier = modifier.fillMaxSize().padding(24.dp),
 verticalArrangement = Arrangement.Center,
 horizontalAlignment = Alignment.CenterHorizontally
 ) {
 Button(onClick = {
 revenue += currentDessert.price
 dessertsSold++
 Log.d(TAG, "Tapped. dessertsSold = $dessertsSold")
 }) {
 Text(currentDessert.name)
 }
 Text("Desserts sold: $dessertsSold")
 Text("Total revenue: ₦$revenue")
 }
}

What you should observe on rotation: onPause, onStop, then Composable left composition, onDestroy, then onCreate, onStart, onResume, Composable entered composition (the exact interleaving of the composable lines with the Activity lines can vary). The point is that the composable is torn down and rebuilt along with the Activity, and rememberSaveable restores the counters.

DisposableEffect(Unit) runs its block once when the composable enters the composition and runs onDispose when it leaves. You will study effects fully in lesson 55.

🎯 Your Challenge

Exercise 2: Add a share button

Dessert Clicker's official version lets the user share a message about their sales. Add a Share button that sends the text I have sold X desserts and made ₦Y! using an implicit intent.

kotlin
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.compose.ui.platform.LocalContext
private fun shareSoldDessertsInformation(context: Context, sold: Int, revenue: Int) {
 val intent = Intent(Intent.ACTION_SEND).apply {
 type = "text/plain"
 putExtra(Intent.EXTRA_TEXT, "I have sold $sold desserts and made ₦$revenue!")
 }
 try {
 context.startActivity(Intent.createChooser(intent, "Share your sales"))
 } catch (e: android.content.ActivityNotFoundException) {
 Toast.makeText(context, "No app can share this", Toast.LENGTH_LONG).show()
 }
}
// Inside DessertClickerScreen:
val context = LocalContext.current
Button(onClick = { shareSoldDessertsInformation(context, dessertsSold, revenue) }) {
 Text("Share")
}

Why: an implicit intent asks Android for any app that can do ACTION_SEND with plain text. createChooser shows the system sharing sheet. We catch ActivityNotFoundException because on an emulator without sharing apps nothing may respond, and the app should not crash. LocalContext.current gives a composable access to the Android Context it needs.

✏️ Your Task

Rotate the device without rememberSaveable and then with it. Press Home and use adb shell am kill with and without it. Fill in a four-cell table of what survived.

Phase 10 of 11
Common Beginner Mistakes (and How to Fix Them)

Mistake #1: Using remember for important state. What happens: counters, typed text and the selected tab reset on rotation. The fix: rememberSaveable for small simple values, or a ViewModel (lesson 24) for larger screen state.

Mistake #2: Forgetting super. The wrong code: an onStart override without super.onStart(). What happens: the app crashes with SuperNotCalledException. The fix: call super first in every lifecycle override.

Mistake #3: Doing slow work in onPause. What happens: the next screen appears late, because onPause must finish before the next Activity can resume. The fix: keep onPause fast. Do heavier clean-up in onStop.

Mistake #4: Assuming onDestroy always runs. What happens: saving data only in onDestroy loses it when the process is killed. The fix: save important data early (as the user works, or in onStop) and use saved instance state for UI state.

Mistake #5: Putting large objects in saved state. What happens: TransactionTooLargeException. The fix: save small identifiers and reload.

Mistake #6: Locking the orientation to avoid the problem. What happens: setting screenOrientation="portrait" hides rotation bugs, but font-size, language, dark-mode and window-resize changes still recreate the Activity, and on large screens the setting may be ignored. The fix: handle recreation properly.

Mistake #7: Starting work in onCreate and never stopping it. What happens: a location or network listener runs while the app is hidden, draining battery. The fix: tie work to onStart/onStop, use lifecycle-aware collection, or a LifecycleEventEffect.

Mistake #8: Testing only on a fast emulator with plenty of memory. What happens: process death never occurs while you test, and the bug appears on a budget phone. The fix: test with adb shell am kill and the Don't keep activities developer option (Settings > Developer options), which destroys each Activity as soon as you leave it.

✏️ Your Task
Practise what you just learned about Common Beginner Mistakes (and How to Fix Them). Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 11 of 11
Knowledge Check

Question 1. In what order do the callbacks run when an app is launched?

onCreate, onStart, onResume.

Question 2. The user presses Home. Which callbacks run, and is the Activity destroyed?

onPause then onStop. The Activity is not destroyed; it stays in memory in the stopped state unless the system later needs the memory.

Question 3. Why does rotating the phone reset a counter stored with remember?

Rotation is a configuration change, so Android destroys and recreates the Activity. remember stores values in the composition owned by the old Activity instance, so the new instance starts empty.

Question 4. What is the difference between rotation and process death for state saved with rememberSaveable?

In both cases rememberSaveable values are restored from the saved instance state. A plain remember value is lost in both. Process death differs because the whole app process is killed (usually in the background) and onDestroy may never run.

Question 5. Why is collectAsStateWithLifecycle() preferred over collectAsState() for Android data streams?

It stops collecting when the UI is not at least in the started state (for example when the app is in the background) and restarts when it returns, which saves battery, data and memory. collectAsState() keeps collecting while the app is hidden.

Go deeper: official resources

✏️ Your Task
Practise what you just learned about Knowledge Check. Open Android Studio, type the examples by hand, change a value, and observe what happens.
🏗️ Build It — Mini Project
Lifecycle-Safe Habit Tracker

Project goal: Build a one-screen water tracker that counts glasses of water drunk today and logs every lifecycle event. It must survive rotation and process death.

Requirements:

  1. A large +1 glass button and a Reset button.
  2. Display Glasses today: N and a message that changes at 4 glasses (Good start), 8 glasses (Goal reached!) and anything beyond (Great, but do not overdo it).
  3. The count survives rotation and adb shell am kill.
  4. Log onCreate, onStart, onResume, onPause, onStop and onDestroy with tag WaterTracker.
  5. Write a short paragraph (in a code comment or a README) describing what you saw in Logcat after one rotation.

Stretch goal: show a third counter, Times the app was opened, that increments in onCreate using savedInstanceState == null to avoid counting rotations. Explain why that test works.

MainActivity.kt
package com.example.watertracker
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
private const val TAG = "WaterTracker"
class MainActivity : ComponentActivity() {
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 Log.d(TAG, "onCreate, fresh launch = ${savedInstanceState == null}")
 setContent {
 MaterialTheme {
 WaterScreen()
 }
 }
 }
 override fun onStart() { super.onStart(); Log.d(TAG, "onStart") }
 override fun onResume() { super.onResume(); Log.d(TAG, "onResume") }
 override fun onPause() { super.onPause(); Log.d(TAG, "onPause") }
 override fun onStop() { super.onStop(); Log.d(TAG, "onStop") }
 override fun onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy") }
}
@Composable
fun WaterScreen(modifier: Modifier = Modifier) {
 var glasses by rememberSaveable { mutableIntStateOf(0) }
 val message = when {
 glasses >= 9 -> "Great, but do not overdo it"
 glasses >= 8 -> "Goal reached!"
 glasses >= 4 -> "Good start"
 else -> "Let us begin"
 }
 Column(
 modifier = modifier.fillMaxSize().padding(24.dp),
 verticalArrangement = Arrangement.Center,
 horizontalAlignment = Alignment.CenterHorizontally
 ) {
 Text("Glasses today: $glasses", style = MaterialTheme.typography.headlineMedium)
 Text(message, modifier = Modifier.padding(vertical = 16.dp))
 Button(onClick = { glasses++ }) { Text("+1 glass") }
 OutlinedButton(
 onClick = { glasses = 0 },
 modifier = Modifier.padding(top = 8.dp)
 ) { Text("Reset") }
 }
}

Expected Logcat after one rotation: onPause, onStop, onDestroy, then onCreate, fresh launch = false, onStart, onResume. The false proves that the system handed back a saved-state Bundle.

Stretch goal answer: savedInstanceState is null only when the Activity is created from scratch. After rotation or process death it is non-null, so if (savedInstanceState == null) { opens++ } counts real launches only. Persist the opened-count somewhere permanent (lesson 35, DataStore) for it to survive the user closing the app.

1

Open your finished project in Android Studio. Check that the .gitignore file the wizard created is in the project root. It keeps local.properties, build folders and keystores out of Git. Never commit passwords or signing keys.

2

Choose Git > Create Git Repository… (or VCS > Enable Version Control Integration in older versions), select the project folder, then open Git > Commit, tick every file, and write the message “Add Android lesson 23 project”.

3

On github.com click New repository, name it android-lesson-23-the-activity-lifecycle, set it to Public, leave Add a README unticked, and click Create repository.

4

Back in Android Studio choose Git > Manage Remotes…, add the repository URL as origin, then choose Git > Push… and push your main branch. Prefer the terminal? Run git remote add origin <your-url> then git push -u origin main.

5

Add a README.md with a screenshot of your app running and a short description. Employers and clients read READMEs first. Your project is now part of your public portfolio. 🎉

Lesson 23 complete! 🎉

You covered:

  • Lesson Introduction
  • Prerequisite Concepts
  • Conceptual Understanding: The Life of a Screen
  • Step-by-Step Build 1: Log Every Lifecycle Event
  • Step-by-Step Build 2: The Dessert Clicker Screen
  • The Problem: Configuration Changes
  • Process Death: The Other Way to Lose State
  • Lifecycle-Aware Work
  • Guided Practice Exercises
  • Common Beginner Mistakes (and How to Fix Them)
  • Knowledge Check
All 72 lessons in Google Android Studio ·Techbase Academy ·© 2025–2026 Techbase Consultant Services, Ibadan
Esc
↑ ↓ to moveEnter to openEsc to close