androidinterview.com

Android Lifecycle Interview Questions

31 questions

Tier
Difficulty
Level

Showing all 31 questions

Activity Lifecycle

What is an Activity and what is its lifecycle?

Tier: EssentialDifficulty: Easy

An Activity is a single screen with a UI, and its lifecycle is the set of callback methods the system calls as that screen moves between being created, visible, focused, backgrounded, and destroyed.

There are seven you should know cold.

  • onCreate() runs once, when the activity is first created. That's where you inflate the layout, set up your ViewModel, and restore any saved state.
  • onStart() fires when the activity becomes visible.
  • onResume() fires when it gains focus and the user can actually interact with it. This is the foreground state.
  • onPause() fires the moment the user starts leaving. It has to be fast, because the next screen is waiting on it.
  • onStop() fires once the activity is no longer visible at all. That's where you do heavier cleanup.
  • onRestart() fires when a stopped activity is about to become visible again, right before onStart(). It's the one people forget, because it only runs on the way back and never on first launch.
  • onDestroy() fires when the activity is finishing or being recreated, for final cleanup.
Activity lifecycle flowThe activity is launched and the system calls onCreate, onStart and onResume, after which the activity is running in the foreground. When the user leaves, the system calls onPause, then onStop when the activity is no longer visible, then onDestroy. If the activity regains focus from the paused state, onResume runs again. If the user navigates back to a stopped activity, onRestart runs and then onStart. A configuration change destroys the activity and immediately recreates it starting from onCreate.
the Activity lifecycle
The activity lifecycle. onPause runs when focus is lost, onStop when the activity is no longer visible, and onDestroy when it is finishing. A configuration change destroys the instance and recreates it from onCreate, which is why state that must survive rotation lives in a ViewModel.

The rule of thumb interviewers are checking for is symmetry. Whatever you start in onResume(), like a camera preview or a sensor listener, you release in onPause(). Whatever you start in onStart(), you release in onStop().

One trap worth mentioning. A configuration change, like a screen rotation, destroys and recreates the activity by default, running the whole onPause through onDestroy then onCreate through onResume sequence again. There is no onRecreate() callback, which people sometimes expect. recreate() is a method you call to trigger that cycle yourself, not something the system calls on you. That's exactly why ViewModel exists, to hold UI state across that destroy and recreate cycle without you having to manually save and restore everything in onSaveInstanceState().

The other callback worth knowing is onNewIntent(), which sits outside that sequence. If an Activity is launched again while an instance already exists, and its launch mode is singleTop, singleTask or singleInstance, Android does not build a second instance and does not call onCreate() again. It calls onNewIntent() on the existing instance, then onRestart(), onStart() and onResume() if it had been stopped.

The gotcha interviewers like here is that getIntent() still returns the original Intent after onNewIntent() runs. You have to call setIntent(intent) yourself inside onNewIntent(), otherwise you keep reading stale data from whenever the Activity was first launched. This is the classic bug behind a notification tap that opens the wrong screen.

The scenario questions interviewers ask

Most of this topic gets asked as small puzzles. Here is the trigger and the exact callback order for the ones that keep coming up.

  • A starts B. A onPause, then B onCreate, onStart and onResume, and only after that A onStop. A stops last on purpose, because the system waits until B is actually on screen before it tears down what A was holding. That is also why onPause has to be tiny. B is waiting on it, so a disk write or a network call there turns into visible jank on every navigation.
  • Back from B to A. B onPause, then A onRestart, onStart and onResume, then B onStop and onDestroy. Same rule, the leaving screen finishes last. A is the same instance, so there is no onCreate.
  • Home on A. onPause and onStop, with onSaveInstanceState next to them. On API 28 and later it runs after onStop, and before API 28 it ran before onStop. Coming back from the launcher gives you onRestart, onStart and onResume, with no onCreate, because the instance was never destroyed.
  • A dialog themed or transparent activity on top of A. A gets onPause and nothing more. It is still partly visible, so it never reaches the stopped state. A plain Dialog, the kind you show from the activity itself, is only a window inside that same activity, so it fires no lifecycle callback at all. People get that second half wrong constantly.
  • Rotation on A. onPause, onStop, onSaveInstanceState, onDestroy, then onCreate with the saved Bundle, onStart, onRestoreInstanceState, onResume. A ViewModel survives, because the system keeps it as the retained non configuration instance and hands it to the new activity. Fields on the activity do not survive. android:configChanges is the opt out, and it is discouraged, because you then owe correct handling of every change you claimed and recreation can still happen for other reasons.
  • Process death in the background, then return. You come back on a brand new process, so onCreate runs with a non null Bundle. The ViewModel is gone, since it only ever lived in memory. What comes back is whatever you wrote in onSaveInstanceState or in a SavedStateHandle, which is the whole reason SavedStateHandle exists.
  • Multi window or split screen. Before Android 10 only one activity is resumed at a time and the rest sit in the started state. On Android 10 and later, multi resume keeps both resumed, and onTopResumedActivityChanged() is the callback that tells you which one currently has focus. That is where you grab or release a singleton resource like the camera or the microphone.
  • An incoming call or the lock screen on A. The full screen case is onPause then onStop, and onRestart, onStart, onResume when the user comes back. A call shown as a heads up overlay is the other case. A stays partly visible, so it only pauses, which is exactly the dialog themed activity rule again.
  • finish() called inside onCreate. onDestroy runs and onStart and onResume never do. The activity is finishing before it was ever visible, so nothing you set up for the visible states gets torn down for you. Clean up whatever you already allocated.
  • A notification opens A while B is on top. With standard, you get a second A instance stacked above B. With singleTop, singleTask or singleInstance, the existing A gets onNewIntent(), then onRestart, onStart and onResume if it was stopped, and no onCreate. The full set of stack puzzles lives in launch modes.

Three of those, drawn out. Time runs left to right and the arrow follows the order the system actually calls things.

ABonPause1onCreate2onStart3onResume4onStop5
A starts B
A stops only after B is on screen, which is why onPause has to be tiny. B is waiting on it.
BAonPause1onRestart2onStart3onResume4onStop5onDestroy6
Back from B to A
Same rule in reverse. The leaving screen finishes last, and A is the same instance, so there is no onCreate.
A (old)A (new)A (old)A (new)onPause1onStop2onSaveInstance…3onDestroy4onCreate5onStart6onRestoreInst…7onResume8
Rotation on A
Two different objects. The dashed boxes are the system destroying one and creating the next, and the ViewModel is what crosses the gap between them.

What all of these are really checking is three habits. That you treat onPause as a place for tiny work only, that onStop is where you release what the screen was holding, and that onDestroy is not guaranteed at all when the system kills your process. If cleanup has to happen, it belongs in onStop, not in onDestroy.

Read more The activity lifecycle (opens in a new tab)

What happens to the Activity if you rotate the device?

Tier: CommonDifficulty: Easy

By default the system treats a rotation as a configuration change, and destroys and recreates the Activity entirely, running onPause(), onStop(), onDestroy(), then a fresh onCreate(), onStart(), onResume() on a brand new instance.

Anything you did not explicitly preserve is gone, views get reinflated, in flight animations reset, and any local variable holding state just disappears with the old instance. This is exactly why ViewModel exists, its instance is handed off to the new Activity instance through a retained store the framework keeps aside from the destroy and recreate cycle, so your UI state does not have to be manually rebuilt from nothing every time.

You can opt out of this behavior by declaring android:configChanges="orientation|screenSize" in the manifest for that Activity, which tells the system not to destroy it for those specific changes, instead just calling onConfigurationChanged() so you can adjust the layout yourself. This is uncommon in practice, most apps let the recreate happen and rely on ViewModel plus onSaveInstanceState() rather than taking on the responsibility of handling every layout adjustment by hand.

What is the difference between onCreate() and onStart()?

Tier: CommonDifficulty: Easy

onCreate() runs once for the entire life of an Activity instance and the Activity is not visible to the user yet, it is where you inflate the layout, set up a ViewModel, and restore any saved state. onStart() is where the Activity actually becomes visible on screen, though it is still not interactive at that point.

The other difference is how often each runs. onCreate() only fires once per instance, right after the Activity is constructed. onStart() can fire many times over that same instance's life, every time the Activity comes back to the screen after being stopped, for example returning from another Activity, onStart() runs again but onCreate() does not.

In practice this means anything that should only ever happen once, setting up view references, creating objects, belongs in onCreate(). Anything that needs to re run each time the screen becomes visible again, restarting a listener you released in onStop() for instance, belongs in onStart().

Read more The activity lifecycle (opens in a new tab)

When is onActivityResult called?

Tier: CommonDifficulty: Easy

onActivityResult() is called on the calling Activity once the Activity it launched with startActivityForResult() finishes and returns a result, and it runs after onRestart() but before onResume() on the way back.

@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
        val uri = data?.data
    }
}

It is deprecated in favor of the Activity Result API, registerForActivityResult(), because request codes had to be managed by hand across the whole Activity or Fragment, which got messy fast once a screen launched more than one thing for a result, and there was no compile time guarantee that the code reading the result matched the code that launched it.

val pickImage = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
    // handle result directly, no request code to match up
}

What are onSaveInstanceState() and onRestoreInstanceState() in an Activity?

Tier: CommonDifficulty: Medium

onSaveInstanceState(outState: Bundle) is called before the Activity is destroyed for a reason the system might not bring it back exactly as it was, a rotation, or the system killing the process in the background to reclaim memory. You pack small, simple values into the Bundle, scroll position, a selected tab, text a user was mid typing.

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putInt("scrollPosition", scrollPosition)
}

onRestoreInstanceState(savedInstanceState: Bundle) is called after onStart(), with that same Bundle, and hands you a guaranteed non null value, unlike onCreate() where the savedInstanceState parameter can be null on a fresh launch. Most people just read it back in onCreate() instead since it is called earlier and the null check is trivial, onRestoreInstanceState() exists mainly for the cases where you want restoration to happen strictly after the rest of onStart() has run.

One thing worth remembering, this Bundle is meant for small amounts of UI state, not your whole app's data, it gets serialized and there are real size limits on how much you can safely stuff into it. It also does not fire when the user presses back and the Activity finishes normally, only when the destruction is not the user's own choice to leave.

What kind of events trigger onPause() to run?

Tier: CommonDifficulty: Medium

onPause() fires whenever the Activity is about to lose focus, which is a broader condition than losing visibility entirely, that is what onStop() is for.

  • The user presses Home, or opens Recents, taking focus away from the app.
  • Another Activity is launched on top of this one, including a translucent or dialog themed Activity that leaves this one partially visible underneath.
  • A system level UI appears over the app, an incoming call, a permission prompt, a system dialog.
  • The user switches focus to a different app window in split screen or multi window mode.
  • The device locks, the screen turns off, or the user starts using another app entirely.

The common thread is loss of focus, not loss of visibility, an Activity can be fully on screen and still onPause(), for example sitting behind a translucent Activity or a system dialog. That is also why onPause() has to be fast, whatever triggered it is usually waiting on it to finish before it can fully take over.

When is only onDestroy called for an activity, without onPause() and onStop()?

Tier: CommonDifficulty: Medium

Honestly, it does not, not in the way the question implies. onPause() and onStop() always run before onDestroy() for a normal Activity finish, the framework does not skip earlier callbacks in that sequence.

The scenario people usually mean by this question is calling finish() very early, inside onCreate() before the Activity is even shown. What actually happens there is that the Activity still runs the full sequence, onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy(), it just happens in one quick burst instead of being spread out over user interaction, which can make it look from a casual glance like the middle steps were skipped when they were not.

The other case that can genuinely look like "only onDestroy() ran" is the system killing the process outright, low memory, a force stop, a crash. That is actually the opposite of the premise, none of the lifecycle callbacks run reliably in that case, onDestroy() included, because the process is simply terminated rather than walked through its normal teardown.

If you are asked this in an interview, the strongest answer is naming the false premise directly, Android's lifecycle callbacks fire in a fixed, guaranteed order for a normal Activity finish, and the two situations that break that expectation either still run every callback in sequence or run none of them at all.

Which callback should you use to know when your activity came to the foreground?

Tier: CommonDifficulty: Medium

Use onResume(), not onStart(). onStart() only tells you the Activity became visible, onResume() is the point it actually has focus and the user can interact with it, which is what most people mean by "came to the foreground."

override fun onResume() {
    super.onResume()
    analytics.logScreenView(screenName)
}

Remember it fires every time the Activity returns to the front, not just on first launch, returning from a dialog, from another Activity, from the recents switcher, or after the screen turns back on, all trigger onResume() again, so anything you put there needs to be safe to run repeatedly.

If what you actually need is knowing when the whole app comes to the foreground, not just this one Activity, onResume() on a single Activity is the wrong tool, since it also fires when navigating between your own screens. That case calls for ProcessLifecycleOwner, which exposes app level foreground and background events independent of which Activity is currently showing.

Fragment Lifecycle

A fragment has a view lifecycle separate from its own, and that gap is what most of these questions are really testing.

What is a Fragment and what is its lifecycle?

Tier: EssentialDifficulty: Easy

A Fragment is a reusable piece of UI and behavior that lives inside an Activity, and its lifecycle is similar to an Activity's but with a few extra states because a Fragment has to attach to a host and manage its own view separately.

The order runs like this.

  • onAttach(), the Fragment gets a reference to its host Activity.
  • onCreate(), the Fragment itself is created, but there's no view yet.
  • onCreateView(), you inflate and return the layout.
  • onViewCreated(), the view now exists, so this is where you set up RecyclerView adapters or observe LiveData.

Then onStart() and onResume() mirror the Activity's, followed on the way out by onPause(), onStop(), onDestroyView(), onDestroy(), and onDetach().

Fragment lifecycle flowThe fragment is added and the system calls onAttach and onCreate. The view lifecycle then begins with onCreateView and onViewCreated, followed by onStart and onResume. On the way down the system calls onPause, onStop and onDestroyView, which ends the view lifecycle, then onDestroy and onDetach. When the fragment goes on the back stack only the view is destroyed, and onCreateView runs again when the user returns, while the fragment instance survives.
the Fragment lifecycle
The fragment lifecycle. The highlighted region is the view lifecycle, which runs from onCreateView to onDestroyView and is what getViewLifecycleOwner tracks. On the back stack only the view is destroyed, the fragment instance survives and the view is recreated on return.

The one thing that actually gets asked in interviews is why there are two separate "destroy" points, onDestroyView() and onDestroy(). A Fragment can be kept alive in the back stack while its view is torn down, for example when you navigate away. So the Fragment object survives but its view doesn't. That's why Fragments expose a separate view lifecycle through viewLifecycleOwner, and why you should always observe LiveData or collect Flows using viewLifecycleOwner, not the Fragment itself. If you use the Fragment's own lifecycle for that, you can leak observers that outlive the view and even get called after the view is gone, which crashes or silently updates a view that no longer exists.

One more thing worth saying out loud. Any view references you grab in onCreateView() should be cleared in onDestroyView() (this is the whole reason for the "clear the binding" pattern with view binding), otherwise you're holding a reference to a destroyed view.

The scenario questions interviewers ask

The rule underneath all of these is that a Fragment can never be in a higher state than its host. The host moves first on the way up and last on the way down.

  • Adding F in the Activity's onCreate(). The transaction is committed there, so F runs onAttach, onCreate, onCreateView and onViewCreated while the Activity is still creating. Then the Activity's onStart is followed by F's onStart, and the Activity's onResume by F's onResume. On the way out the order flips, F pauses and stops before its host does.
  • Replacing F with G, with addToBackStack. F runs onPause, onStop, onDestroyView, and stops there. No onDestroy, no onDetach. The instance stays alive in the back stack with only its view torn down. Press Back and the same instance gets onCreateView and onViewCreated again with a brand new view, then onStart and onResume. That is why view references must be cleared in onDestroyView() and why viewLifecycleOwner exists at all.
  • Replacing F without addToBackStack. F goes the whole way, onPause, onStop, onDestroyView, onDestroy, onDetach. The instance is gone and nothing about it comes back.
  • Rotation with a Fragment on screen. It is destroyed and recreated along with the Activity. The arguments Bundle survives, and so does anything you wrote in the Fragment's onSaveInstanceState. Plain fields do not. A ViewModel scoped to the Fragment survives. The FragmentManager restores the Fragment for you, so committing it again in the Activity's onCreate() without checking savedInstanceState == null gives you two copies stacked on each other. That is one of the most common bugs on this topic.
  • ViewPager or tabs. Off screen pages get created but should not be resumed, otherwise every tab starts its own analytics ping or video player. FragmentStateAdapter on ViewPager2 uses setMaxLifecycle() to cap off screen pages at the started state, so only the visible one reaches resumed. On the old ViewPager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT is the setting that does the same job.
  • A DialogFragment shown and dismissed. Showing it runs the normal creation path, onAttach through onResume, and the host Activity gets no callback, because the dialog is a window inside it. Dismissing runs onPause, onStop, onDestroyView, onDestroy, onDetach, unless you added it to the back stack, in which case it stops at onDestroyView like any other Fragment.
  • Nested child fragments. A child can never be in a higher state than its parent, so the parent starts first and the child stops first. Use childFragmentManager for fragments hosted inside this Fragment, so they are torn down with it, and parentFragmentManager for the container the Fragment itself lives in. Using the wrong one is how children leak or get restored into the wrong host.
  • Two lifecycles, one classic bug. Collecting a Flow in lifecycleScope inside onViewCreated() looks fine until you navigate away and come back. The Fragment instance was never destroyed, so the old collector is still alive, onViewCreated() runs again, and now you have two. The updates arrive twice and can hit a view that no longer exists. Use viewLifecycleOwner.lifecycleScope with repeatOnLifecycle, which dies with the view.

The back stack case, drawn out, because it is the one people get wrong.

FGFGonPause1onStop2onDestroyView3onCreateView4onResume5onDestroy6onCreateView7onViewCreated8onStart9onResume10
Replace F with G, then Back
F never reaches onDestroy. The same instance gets a brand new view on the way back, so anything holding the old view in onDestroyView has to let go of it.

What the interviewer is really checking is that you know there are two lifecycles here, the Fragment's and its view's, and that the FragmentManager restores fragments for you. Candidates who miss the first one leak observers. Candidates who miss the second one add duplicate fragments on every rotation.

Read more The fragment lifecycle (opens in a new tab)

How many callbacks are there in Fragments?

Tier: CommonDifficulty: Easy

There are eleven core callbacks you should be able to list in order.

  • onAttach(), the Fragment gets a reference to its host Activity.
  • onCreate(), the Fragment object itself is created, no view yet.
  • onCreateView(), you inflate and return the layout.
  • onViewCreated(), the view now exists.
  • onStart(), the Fragment becomes visible.
  • onResume(), the Fragment gains focus and is interactive.
  • onPause(), the Fragment is losing focus.
  • onStop(), the Fragment is no longer visible.
  • onDestroyView(), the view is torn down, the Fragment object can still be alive after this.
  • onDestroy(), the Fragment object itself is being cleaned up.
  • onDetach(), the Fragment loses its reference to the host Activity.

Two more show up depending on what you count. onViewStateRestored() runs right after onViewCreated() once the saved view hierarchy state has been restored, and onSaveInstanceState() runs on the way out to let the Fragment save state into a Bundle. Most interviewers are checking that you know the two destroy points, onDestroyView() versus onDestroy(), matter more than the exact total count.

Explain Fragments and their lifecycle, the Activity lifecycle, Views and Layouts.

Tier: CommonDifficulty: MediumAsked at: booking-com

This is really four questions bundled into one, and interviewers use it as a starting point to see how much ground you can cover before they drill into whichever area interests them.

  • A Fragment is a reusable piece of UI and behavior hosted inside an Activity, with its own lifecycle that layers on top of the Activity's, onAttach(), onCreate(), onCreateView(), onViewCreated(), then onStart() and onResume() mirroring the Activity, and on the way out onPause(), onStop(), onDestroyView(), onDestroy(), onDetach().
  • The Activity lifecycle is the outer container for all of this, onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy(), with a Fragment's state capped by whatever state its host Activity is in.
  • A View is a single drawable, interactive element, a Button or a TextView. A ViewGroup is a container that holds other Views and ViewGroups and is responsible for measuring and positioning its children.
  • A Layout is a ViewGroup subclass with a specific arrangement strategy, LinearLayout stacks children in a line, ConstraintLayout positions children relative to each other or the parent using constraints, and layouts are what you inflate from XML into an actual View tree at runtime.

The follow up worth preparing for is how these pieces interact, a Fragment's onCreateView() is where you inflate its layout, and the resulting View tree only lives as long as the Fragment's separate view lifecycle, which is shorter than the Fragment object's own lifecycle since a Fragment can sit in the back stack with its view destroyed but the Fragment instance still alive.

Read more The activity lifecycle (opens in a new tab)The fragment lifecycle (opens in a new tab)

What are the Lifecycle States (INITIALIZED, CREATED, STARTED, RESUMED, DESTROYED) and which callbacks map to them?

Tier: CommonDifficulty: Medium

Lifecycle.State is the enum the Jetpack Lifecycle library uses underneath the callback methods, and it is what repeatOnLifecycle() and lifecycle aware components actually check against, rather than the callback names themselves.

  • INITIALIZED, the starting state right after the object is constructed, before any callback has run.
  • CREATED, reached once onCreate() has run, and also the state an Activity or Fragment falls back to after onStop().
  • STARTED, reached once onStart() has run, and the state it falls back to after onPause().
  • RESUMED, reached once onResume() has run, this is the only state where the component is actually in the foreground and interactive.
  • DESTROYED, the terminal state, reached once onDestroy() has run, an object never leaves this state.

For a Fragment specifically, this matters more than it sounds, because the Fragment's own Lifecycle and its view's Lifecycle track this enum independently. The Fragment's Lifecycle moves CREATED to DESTROYED across onCreate() through onDestroy(), while viewLifecycleOwner's Lifecycle only exists between onCreateView() and onDestroyView(). That's why repeatOnLifecycle(Lifecycle.State.STARTED) called on viewLifecycleOwner.lifecycle is the safe way to collect a Flow, the block automatically stops and restarts as the view crosses that STARTED boundary instead of you tracking callback pairs by hand.

Read more The fragment lifecycle (opens in a new tab)

What is the difference between a Fragment Lifecycle Observer and a View Lifecycle Observer?

Tier: CommonDifficulty: Hard

A Fragment Lifecycle Observer watches getLifecycle(), the Fragment object's own lifecycle, which spans from onAttach() all the way to onDetach(). A View Lifecycle Observer watches viewLifecycleOwner.lifecycle, which only exists between onCreateView() and onDestroyView().

The gap between those two matters because a Fragment can be kept alive in the back stack while its view is destroyed, for example after you navigate away from it. The Fragment object survives that, the view does not.

// wrong, can outlive the view and crash or update a destroyed view
viewModel.items.observe(this) { updateList(it) }

// right, tied to the view's own lifecycle
viewModel.items.observe(viewLifecycleOwner) { updateList(it) }

If you observe LiveData or collect a Flow using the Fragment's own lifecycle instead of viewLifecycleOwner, the observer can keep firing after onDestroyView() has already torn down the view it is meant to update, which either crashes trying to touch a null binding or silently updates a view that is no longer on screen. Always use viewLifecycleOwner for anything view related, and reserve the Fragment's own lifecycle for things that genuinely need to live as long as the Fragment object does.

ViewModel & State Retention

How would you preserve Activity state during a screen rotation?

Tier: EssentialDifficulty: Easy

The right tool depends on what kind of state it is, and most real screens end up using two of these together.

  • UI state that is expensive to rebuild, a list you fetched from the network, form data the user typed, filters the user applied, belongs in a ViewModel. Rotation destroys and recreates the Activity, but the ViewModel instance survives that cycle untouched, so the new Activity just reconnects to the same object and the screen redraws itself from whatever is already there.
  • Small, simple values the system itself needs to restore, like scroll position or a selected tab index, belong in onSaveInstanceState(outState: Bundle). The system hands that Bundle back to you in onCreate(), or via onRestoreInstanceState(), after recreation.
  • Anything that needs to survive process death, not just rotation, needs to actually be persisted, either through SavedStateHandle inside the ViewModel, which is backed by that same Bundle mechanism, or written to disk or a database.

The distinction interviewers are listening for is that a ViewModel alone is not enough, it protects you from rotation but not from the system killing your process in the background, which is what onSaveInstanceState() and SavedStateHandle are actually for.

What is a ViewModel and how is it useful?

Tier: EssentialDifficulty: Easy

A ViewModel is a class that holds and manages UI-related data so that it survives configuration changes, like a screen rotation, without you having to manually save and restore it.

Without a ViewModel, if you load a list of results in onCreate() and the user rotates the phone, the Activity gets destroyed and recreated, and you'd have to fetch that data all over again, or stash it in a Bundle, which only works for small amounts of data. A ViewModel is scoped to the Activity or Fragment's lifecycle, but the framework keeps the same ViewModel instance alive across a configuration change and only actually clears it when the screen is truly finishing, calling onCleared() at that point so you can cancel jobs or release resources.

The other big benefit is separation of concerns. Your ViewModel holds the state and the logic for producing it, usually exposed as LiveData or StateFlow, and your Activity or Fragment just observes that state and renders it. That keeps business logic out of the Activity, which makes it testable without needing an actual Android device or emulator, since ViewModel itself doesn't hold a reference to a View or Context.

class UserViewModel : ViewModel() {
    private val _user = MutableStateFlow<User?>(null)
    val user: StateFlow<User?> = _user

    fun loadUser(id: String) {
        viewModelScope.launch {
            _user.value = repository.getUser(id)
        }
    }
}

One thing worth flagging. A ViewModel is not for long-term persistence. It survives rotation, but it does not survive process death or the user leaving the app entirely. That's what SavedStateHandle or actual storage is for.

How does ViewModel work internally?

Tier: EssentialDifficulty: Medium

A ViewModel survives configuration changes because it's not actually owned by the Activity. It's held in a separate ViewModelStore object that the Android framework keeps alive across the destroy and recreate cycle, and only throws away when the Activity is finishing for real.

Your Activity or Fragment implements ViewModelStoreOwner, which just means it holds a ViewModelStore, essentially a map of ViewModel instances keyed by class name (or a key you provide). When you call by viewModels() or ViewModelProvider(this).get(MyViewModel::class.java), the ViewModelProvider first checks that store. If a ViewModel already exists there, it hands you back the same instance instead of creating a new one.

On a configuration change, the framework retains that ViewModelStore in a special non-configuration instance and reattaches it to the new Activity instance that gets created right after. So the Activity object itself is brand new, but the ViewModelStore, and every ViewModel inside it, is literally the same object as before. That's the whole trick. Nothing is being serialized or restored. It's just not thrown away in the first place.

The store only gets cleared, and onCleared() called on each ViewModel, when the Activity is actually finishing, meaning isFinishing() is true and it's not just a rotation. That's also when viewModelScope, which is backed by a Job tied to onCleared(), gets cancelled, which is why any coroutines you launch in viewModelScope stop automatically instead of leaking.

How do you share a ViewModel between Fragments (SharedViewModel)?

Tier: CommonDifficulty: Medium

You scope the ViewModel to the shared host instead of to each Fragment individually, usually the parent Activity, so both Fragments get back the same instance.

class SharedViewModel : ViewModel() {
    val selectedItem = MutableLiveData<Item>()
}

class FragmentA : Fragment() {
    private val sharedViewModel: SharedViewModel by activityViewModels()
}

class FragmentB : Fragment() {
    private val sharedViewModel: SharedViewModel by activityViewModels()
}

activityViewModels() asks the hosting Activity's ViewModelStore for a SharedViewModel, not the Fragment's own store. Since both Fragments point at the same store and the same key, ViewModelProvider hands back the identical instance to each of them. FragmentA writing to selectedItem is immediately visible to FragmentB observing it, no direct Fragment to Fragment reference needed.

If two Fragments belong to the same navigation graph but not the same Activity relationship you want to lean on, navGraphViewModels() does the same trick scoped to the graph's back stack entry instead, which is tighter than sharing at the Activity level.

When does a ViewModel not survive?

Tier: CommonDifficulty: Medium

A ViewModel does not survive whenever its owner is finished for good, rather than just recreated, and it never survives process death at all.

  • The user presses back and the Activity finishes, or you call finish() yourself, onCleared() runs and the ViewModel is gone.
  • A Fragment is removed through a transaction that did not add it to the back stack, so it is not coming back the way a back stack entry would restore it.
  • The system kills the whole process in the background to reclaim memory. Rotation survives because the process itself stays alive and the retained store just gets handed to the new instance, process death takes the entire process, retained store included, with it.

The one thing that reliably survives process death, unlike the ViewModel object itself, is whatever you stored in its SavedStateHandle, since that is backed by the same saved instance Bundle mechanism the system writes to disk before killing the process. So a ViewModel surviving rotation is not the same guarantee as a ViewModel surviving everything, it is specifically scoped to configuration changes, not to the process being killed.

How is a ViewModel instance provided to an Activity and a Fragment? How does ViewModelStore decide when to retain the instance?

Tier: CommonDifficulty: Hard

ViewModelProvider(owner).get(MyViewModel::class.java) looks up an existing instance in the owner's ViewModelStore by key, and only creates a new one through the factory if nothing is there yet. The owner, an Activity or a Fragment, is a ViewModelStoreOwner, and that store is what actually holds the ViewModel objects.

The retention trick is that the ViewModelStore itself is not tied to the Activity or Fragment instance that gets destroyed on a configuration change. It is stashed in a special non configuration instance the framework keeps aside specifically to survive recreation. So when the Activity is destroyed and rebuilt after a rotation, the new Activity instance is handed back the same ViewModelStore, and ViewModelProvider finds your ViewModel already sitting in it instead of creating a fresh one.

onCleared() is called, and the store is actually thrown away, only when the owner is going away for good, the Activity finishes because the user pressed back or you called finish(), or the Fragment is removed and not on the back stack. A rotation never triggers that path, which is exactly why a ViewModel survives rotation but not a deliberate exit, and never survives process death, since the whole process including that retained store is gone at that point.

Process Death & Configuration Changes

A rotation and a killed process are different failures. Answering as though they are the same is the usual mistake.

What is process death?

Tier: EssentialDifficulty: Medium

Process death is the system killing your app's entire process while it is in the background, usually to reclaim memory for whatever the user is actively using. It is different from the destroy and recreate cycle a rotation triggers, that keeps the process alive and just rebuilds the Activity, process death ends the process itself, taking every in memory object with it.

That distinction matters because of what survives each one.

  • A ViewModel survives rotation, since its retained store lives inside the process, but it does not survive process death, the whole process including that store is gone.
  • onSaveInstanceState() state does survive process death, because the system writes that Bundle out to disk before killing the process, and hands it back to onCreate() if the user returns to that same task later.
  • SavedStateHandle inside a ViewModel is the bridge between the two, it is backed by that same saved Bundle mechanism, so values you put there come back even after a real process death, not just a rotation.

You can trigger process death on purpose while debugging by running adb shell am kill <package name> after backgrounding the app, then reopening it from Recents, which is the honest way to check whether your screen actually restores state instead of just surviving rotation.

Less common, worth knowing

These come up less often. Skim them once you are comfortable with everything above.

Activity Lifecycle

What is the lifecycle in PIP (Picture-in-Picture) mode?

Tier: Less commonDifficulty: Medium

An Activity in Picture in Picture mode is paused but visible, it sits in onPause() for as long as it stays shrunk down to that floating window, rather than moving on to onStop().

  • Entering PIP, onUserLeaveHint() fires first, then onPictureInPictureModeChanged(true, configuration), then onPause(). It stops there, onStop() never runs, because the Activity is still fully visible, just small and without focus.
  • While in PIP, the window never has input focus, so it just stays paused the whole time it is floating, which is why you should not release resources like a video player in onPause() the way you normally would, doing so would freeze the very thing PIP is meant to keep playing.
  • Leaving PIP, either the user taps it to expand back to full screen or closes it, onPictureInPictureModeChanged(false, configuration) fires, followed by onResume() if it is expanding back, or the full stop and destroy sequence if it is being closed.

The practical implication is that your symmetry rule for onPause() and onResume(), releasing a camera or a sensor in onPause(), has to be PIP aware, checking isInPictureInPictureMode before you tear anything down in onPause() is what keeps video or camera preview actually playing while shrunk into the corner.

Which callback gets called on an Activity when an AlertDialog is shown?

Tier: Less commonDifficulty: Medium

None of the usual lifecycle callbacks fire, an AlertDialog is its own floating window, not a new Activity, so the Activity underneath stays RESUMED for as long as it is showing.

The only thing that actually changes is onWindowFocusChanged(hasFocus: Boolean), which the system calls with false once the dialog takes input focus, and again with true once the dialog is dismissed and focus returns to the Activity's own window.

override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)
    // hasFocus is false while the AlertDialog is showing
}

This trips people up because it feels like the dialog should count as something covering the Activity, but visibility and interactivity are not what onPause() and onStop() track, focus and foreground status are. The Activity is still fully visible and still the foreground component, it has just handed keyboard and touch focus to the dialog sitting on top of it.

What callbacks trigger when a Dialog opens, when attached from the same activity/fragment versus another one?

Tier: Less commonDifficulty: Hard

Neither case triggers the usual lifecycle callbacks on the host, because a Dialog is a separate window floating on top, not a new Activity. The Activity or Fragment underneath stays RESUMED the entire time it is showing. The only thing that fires is onWindowFocusChanged(false), since the window under the dialog loses input focus even though it is still fully visible and in the foreground.

  • Attached from the same Activity or Fragment, through dialog.show() or DialogFragment.show(fragmentManager, tag), the underlying screen keeps running exactly as before, onWindowFocusChanged(false) fires, nothing else does.
  • Attached via a DialogFragment shown against a different Fragment's childFragmentManager rather than the Activity's supportFragmentManager, the behavior is the same on the host side, the only difference is which FragmentManager owns the dialog, which affects whose lifecycle the DialogFragment itself is scoped to, not what happens to the screen underneath.

When the dialog is dismissed, onWindowFocusChanged(true) fires as the underlying window regains focus. If instead of a dialog you launched an actual new Activity, that is a different story entirely, the underlying Activity would go through onPause() and possibly onStop(), which is the mistake people make when they answer this question, they describe Activity transition behavior for something that never left the current screen.

What could be the reasons why onPause did not get triggered?

Tier: Less commonDifficulty: Hard

Under normal operation onPause() is one of the callbacks the system guarantees, so if it genuinely never runs the cause is almost always in your own code, not the framework skipping it.

  • You overrode onPause() and forgot to call super.onPause(). This is the most common cause by far, the framework's own bookkeeping inside the base implementation gets skipped, which can cascade into other lifecycle problems even if your own code in the override runs fine.
  • The Activity crashes before it ever leaves the foreground, an unhandled exception during onResume() or from a background thread kills the process outright, and no further lifecycle callback runs at all.
  • The process gets killed directly by the system under severe memory pressure, or force stopped by the user or by ADB. Process death does not politely walk through onPause(), onStop(), onDestroy(), it just ends the process.
  • The work you expected to see happen in onPause() is actually somewhere else, like onStop() or onSaveInstanceState(), so it looks like onPause() did not fire when really the logic was just placed in the wrong callback.

If you are debugging this for real, the fastest way to tell which of these it is, is to check whether onDestroy() ran either. If nothing at all ran, it is a process kill or a crash, not a missing onPause().

Fragment Lifecycle

What do launchWhenCreated, launchWhenStarted and launchWhenResumed do?

Tier: Less commonDifficulty: Medium

They were extension functions on Lifecycle and LifecycleOwner that paused a coroutine until the Lifecycle reached a given state, launchWhenStarted { } would suspend its block until STARTED, and pause it again if the Lifecycle dropped back below that, resuming once it crossed the threshold again.

lifecycleScope.launchWhenStarted {
    viewModel.uiState.collect { render(it) }
}

The problem is what "pause" actually meant. The coroutine was suspended, not cancelled, so it stayed alive in memory the whole time the Lifecycle was below the threshold, and if it was collecting a hot Flow, it kept collecting in the background even while the screen was fully stopped, just not delivering anything to your UI until resumed. That wastes work, and worse, a burst of updates while paused could all land on the UI at once the moment it resumed. They are deprecated for this reason.

The replacement is repeatOnLifecycle(Lifecycle.State.STARTED), run inside lifecycleScope.launch { }, which actually cancels the block when the Lifecycle drops below the state and starts a brand new coroutine when it crosses back up, instead of leaving a suspended one hanging around.

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { render(it) }
    }
}

Why is onActivityCreated now deprecated in Fragment?

Tier: Less commonDifficulty: Medium

onActivityCreated() was meant to tell a Fragment that its host Activity's onCreate() had finished, but by the time it ran the Fragment already had a fully created view from onCreateView() and onViewCreated(), so it was really just a second, redundant callback firing right after onViewCreated() in almost every practical case.

It was deprecated because it tied Fragment setup timing to the Activity's lifecycle instead of the Fragment's own view lifecycle, which is the wrong thing to depend on, especially now that Fragments are expected to manage their view setup through viewLifecycleOwner rather than reasoning about the host Activity directly.

// deprecated
override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    setupRecyclerView()
}

// current
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    setupRecyclerView()
}

The practical fix is simply to move whatever was in onActivityCreated() into onViewCreated(), which runs at effectively the same point for nearly all real use cases, view setup, adapter wiring, observing LiveData, without depending on Activity level timing at all.

Does onCreateView get called after returning to a fragment from a fragment on top of it?

Tier: Less commonDifficulty: Hard

It depends on whether the fragment underneath was replaced or just added on top.

  • If the previous fragment transaction used replace(), the fragment's view was destroyed when it left the screen, onDestroyView() ran on it. So coming back through popBackStack() calls onCreateView() again to rebuild that view from scratch, followed by onViewCreated(), onStart() and onResume(). The Fragment object itself was never destroyed though, only its view, so onCreate() does not run again.
  • If the previous transaction used add() instead, the fragment underneath was never removed, its view kept existing the whole time under the new fragment. Popping the back stack just tears down the fragment on top, no onCreateView() call happens on the one underneath because its view never went away.

The practical takeaway is that replace() is more memory efficient since only one fragment's view exists at a time, but it pays for that with a rebuild every time you navigate back. add() keeps views alive so navigating back is cheaper, at the cost of holding more view hierarchies in memory at once.

Explain the Fragment lifecycle inside a ViewPager when sliding between fragments/tabs.

Tier: Less commonDifficulty: Hard

With ViewPager2 and FragmentStateAdapter, only the current page's fragment sits in RESUMED. Its neighbors are kept alive but capped at STARTED, and pages further away have their views destroyed entirely to save memory, while the Fragment objects themselves stay around in the FragmentManager with their state saved.

As you swipe from one tab to the next, here is what actually happens.

  • The outgoing fragment drops from RESUMED to STARTED, it does not go through onDestroyView() immediately, it just loses focus. This is why onPause() fires but onStop() does not right away, since it is still nearby and likely to be swiped back to.
  • The incoming fragment moves up to RESUMED, calling onResume().
  • If you keep swiping further away, fragments outside the adapter's offscreen limit get onDestroyView() called on them, tearing down their view, while the Fragment object survives so it can rebuild that view cheaply when you swipe back.

The older FragmentStatePagerAdapter behaved differently, it kept a wider window of fragments resumed at once rather than capping neighbors at STARTED, which is part of why it was replaced. The practical implication for you as a developer is that you cannot assume onPause() and onResume() map cleanly to "off screen" and "on screen" inside a ViewPager, a fragment can be paused while still fully visible next to the current page.

What fragment callbacks fire when moving from one fragment to another and coming back?

Tier: Less commonDifficulty: Hard

It depends on whether you used replace() or add(), and the answer changes again on the way back.

Moving forward with replace(), added to the back stack.

  • FragmentA, onPause(), onStop().
  • FragmentB, onAttach(), onCreate(), onCreateView(), onViewCreated().
  • FragmentA, onDestroyView(), its view is gone but the object survives, since replace() tears down the outgoing view.
  • FragmentB, onStart(), onResume().

Coming back with popBackStack().

  • FragmentB, onPause(), onStop(), onDestroyView(), onDestroy(), onDetach(), it is leaving for good this time, nothing was keeping it around.
  • FragmentA, onCreateView(), onViewCreated(), its view has to be rebuilt since replace() destroyed it earlier, onCreate() does not run again though.
  • FragmentA, onStart(), onResume().

If you use add() instead of replace(), FragmentA's view was never destroyed in the first place, so popping back to it triggers none of FragmentA's callbacks at all, it was sitting there the whole time underneath FragmentB.

ViewModel & State Retention

How do you keep a video in a playing state when the screen is rotated?

Tier: Less commonDifficulty: Medium

The reliable way is to stop the Activity from being destroyed and recreated on rotation at all, or to keep the player object alive independently of the Activity so playback never actually pauses.

  • Declare android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" on the Activity in the manifest. The system then skips the destroy and recreate cycle for those changes and just calls onConfigurationChanged(), where you resize the video surface for the new orientation instead of tearing the whole screen down. Playback never stops because nothing was ever destroyed.
  • Alternatively, hold the player instance, an ExoPlayer for example, somewhere that survives the Activity's destruction, like a retained ViewModel or a foreground Service. The player keeps decoding and playing audio and video in memory the whole time, and when the Activity recreates you just reattach the new PlayerView surface to the still running player instead of rebuilding it.

The first approach is simpler and is the standard answer for a rotating video player. The second is what you reach for when the player also needs to survive things a config change flag cannot cover, like the user briefly leaving the app entirely.

What does setRetainInstance do and how can you avoid it?

Tier: Less commonDifficulty: Medium

setRetainInstance(true) told the framework to keep the Fragment object itself alive across a configuration change, only its view would be destroyed and recreated, the Fragment instance and any fields on it survived untouched. It existed in the pre ViewModel days as a way to hold onto things like a running background task across rotation without losing the reference.

class OldFragment : Fragment() {
    init {
        retainInstance = true
    }
}

The problem is that it drags the entire Fragment object along, including its Context reference and its relationship to the FragmentManager, which is a much heavier and leakier thing to keep alive than the actual state you cared about. It was deprecated because ViewModel does this same job properly, a ViewModel holds only the state and logic you actually need to survive rotation, is automatically scoped to the Fragment's own ViewModelStore, and carries no reference to the View, the Fragment, or the Activity, so there is nothing to leak.

You avoid setRetainInstance entirely by moving whatever state you were protecting into a ViewModel and letting the Fragment be destroyed and recreated normally, it just reconnects to the same ViewModel instance on the way back up.