B01 — Hello lifecycle
Build an activity that logs every lifecycle callback, rotate the device, and watch what the system actually does. The smallest project that makes a platform mechanism visible.
An activity's lifecycle is the contract between your code and the system. It is also the first place a platform mechanism becomes observable: you can log every callback and watch the system call them in response to a rotation, a home press or a notification.
What you are building#
One activity with a logger that writes every lifecycle callback to logcat, plus a counter that survives a rotation. The screen has a button that increments the counter and a text view that shows it, so you can see the difference between state that is lost and state that is saved.
Steps#
- Create the project
Create a new Compose project in Android Studio with an empty activity. Note the package name; you will filter logcat by it.
You should seeA new project that builds and runs on the emulator. - Add a tag and a logger
Define a constant tag, for example
LifecycleDemo, and a small function that logs a message with that tag. You will call it from every callback.You should seeA single TAG constant you can grep for. - Log the activity callbacks
Override
onCreate,onStart,onResume,onPause,onStop,onDestroyand log each one. In Compose, the activity is still the lifecycle owner; the composables follow it.You should seeonCreate, onStart, onResume appear in order at launch. - Add the counter
Hold the counter in a
rememberSaveableso it survives a configuration change. A plainrememberdoes not, which is the point of the next step.You should seeThe counter increments when you tap. - Watch a rotation
Rotate the emulator. Filter logcat by your tag and read the sequence: the activity is destroyed and recreated, and
rememberSaveablerestores the counter from the saved instance state.You should seeA destroy/create pair, then start and resume again, with the counter preserved. - Break it deliberately
Change
rememberSaveabletorememberand rotate again. The counter resets, because a plainrememberis scoped to the composition, not to the saved state.You should seeThe counter resets on rotation. - Put the counter in a ViewModel
Move the counter into a
ViewModel. Rotating no longer loses it, because the ViewModel outlives the activity instance. Compare this withrememberSaveable: both survive a rotation, and they fail differently when the process is killed.You should seeThe counter survives a rotation without saved state. - Kill the process
Use the emulator's device state or
adb shell am kill <package>to kill the process while the app is backgrounded, then reopen it. This is the difference between a configuration change and process death, and it is the reason saved state exists.You should seeThe ViewModel value is lost; the saved-state value is restored.
The logging is one line per callback:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.i("LifecycleDemo", "onCreate")
setContent { /* ... */ }
}
override fun onStart() { super.onStart(); Log.i("LifecycleDemo", "onStart") }
override fun onResume() { super.onResume(); Log.i("LifecycleDemo", "onResume") }
override fun onPause() { Log.i("LifecycleDemo", "onPause"); super.onPause() }
override fun onStop() { Log.i("LifecycleDemo", "onStop"); super.onStop() }
override fun onDestroy() { Log.i("LifecycleDemo", "onDestroy"); super.onDestroy() }
}The two mechanisms in code:
// Survives a configuration change, not process death.
val counter by viewModel.count.collectAsStateWithLifecycle()
// Survives both, because it is written to the saved instance state.
var saved by rememberSaveable { mutableIntStateOf(0) }adb logcat -c
adb logcat -s LifecycleDemo
adb shell am start -W -n com.example.lifecycledemo/.MainActivity
adb shell input keyevent KEYCODE_HOME
adb shell am start -W -n com.example.lifecycledemo/.MainActivityExample output
Illustrative output — your timestamps and PIDs differ.Verification#
You should be able to answer three questions from the log alone:
- What is the order of callbacks on a cold launch?
- Which callbacks run when the app goes to the background, and which do not?
- What happens to the activity instance on a rotation?
If the answers are not visible in the log, add logging to the composables' lifecycle as well and
compare. Compose's LifecycleEventEffect or a DisposableEffect shows where composition differs
from the activity.
What you learned#
- The lifecycle is a contract, not a suggestion: the system calls the callbacks in a defined order and your code must survive them.
- Configuration changes recreate the activity. Saved state and ViewModels are two different answers to that, with different lifetimes.
- Process death is a third case. Saved state survives it; a ViewModel does not.
- Logging is the cheapest observability tool you have. A tag and a filter turn an invisible contract into a sequence you can read.
Read Activity lifecycle for the full callback map, and Lab 4 — logcat for the filtering you just used.
Extend it#
- Add a second activity and log both lifecycles during a transition. You will see the ordering between the outgoing and incoming activity, which is the part people get wrong.
- Add a
ViewModelwith a saved-state handle so the value survives both a rotation and process death, and compare the two mechanisms. - Trigger a configuration change without rotating using
adb shell cmd activityor by changing the font scale in Settings, and compare the callback sequence.
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsThe activity lifecycledeveloper.android.comaccessed 2026-10-27↩
- docs活動簡介 | App architecturedeveloper.android.comaccessed 2026-10-27↩
- docsAndroid Debug Bridge (adb)developer.android.comaccessed 2026-10-27↩
- docsProcesses and app lifecycledeveloper.android.comaccessed 2026-10-27↩
- docsLifecycle of composablesdeveloper.android.comaccessed 2026-10-27↩