Android Fragment Lifecycle

The Android Fragment lifecycle runs through 12 callbacks in order: onAttach() → onCreate() → onCreateView() → onViewCreated() → onViewStateRestored() → onStart() → onResume() → onPause() → onStop() → onDestroyView() → onDestroy() → onDetach(). Since AndroidX Fragment 1.3, these callbacks are driven by two separate Lifecycle objects — one for the fragment itself and one for its view — each moving through the states INITIALIZED → CREATED → STARTED → RESUMED → DESTROYED. Get the callback-to-state mapping wrong and you get the two most common Fragment bugs in Android: memory leaks from views that outlive their lifecycle, and crashes from touching a view after onDestroyView() has already run.
Jump to: lifecycle states · all 12 callbacks · fragment vs activity lifecycle · the view lifecycle · a working AndroidX example · common mistakes
What Is a Fragment in Android?
A Fragment is a reusable piece of UI and behavior that lives inside a host Activity (or another Fragment, as a "child fragment"). Fragments let you build a screen out of independent, swappable modules — a list on one side and a detail pane on the other for tablets, or a single pane that swaps content on phones — without duplicating logic across activities.
- A fragment must always be hosted by an
Activityor a parentFragment; it cannot exist on its own. - It has its own lifecycle, but that lifecycle can never outrank its host's.
- Multiple fragments can live in one activity, and the same fragment class can be reused across different activities.
- Fragments can be added, replaced, or removed at runtime through the
FragmentManager. - Modern code should use
androidx.fragment.app.Fragmentfrom the Jetpack Fragment library. The oldandroid.app.Fragment(deprecated since API 28) andandroid.support.v4.app.Fragment(the pre-AndroidX support library) should not be used in new projects — both are dead ends.
Fragment Lifecycle States
Under the hood, every AndroidX Fragment is a LifecycleOwner. Its state is one of five values from the Lifecycle.State enum:
| State | Meaning |
|---|---|
INITIALIZED | The fragment object has been instantiated but has not yet been added to a FragmentManager. |
CREATED | The fragment is attached to its host and has run onCreate(). Its view may or may not exist yet. |
STARTED | The fragment is visible on screen but does not have user focus yet. Its view is guaranteed to exist. |
RESUMED | The fragment is visible and has focus — the user can interact with it. |
DESTROYED | The fragment has been removed, or its FragmentManager has been destroyed. The lifecycle has ended. |
A fragment can never exceed the state of its own FragmentManager, and a child fragment can never exceed the state of its parent fragment or activity — the parent must reach STARTED before its children can, and children must drop back to CREATED before their parent does. As a fragment is pushed onto the back stack it moves upward, CREATED → STARTED → RESUMED; when it is popped off, it moves back down, RESUMED → STARTED → CREATED → DESTROYED.
All 12 Fragment Lifecycle Callbacks, in Order
Here is every callback the AndroidX Fragment class exposes, in the exact order Android calls them the first time a fragment is created and displayed.
| # | Callback | What to do here |
|---|---|---|
| 1 | onAttach(Context) | Fragment is attached to its host activity. Safe to grab a reference to the host via the passed-in Context (cast it, or use interfaces — never store the old-style target-fragment pattern). |
| 2 | onCreate(Bundle?) | Initialize non-view state: retained data, ViewModel lookups, argument parsing from arguments. There is no view yet — do not touch layout here. |
| 3 | onCreateView(LayoutInflater, ViewGroup?, Bundle?) | Inflate and return the fragment's root View. Do not run business logic here beyond inflating layout. |
| 4 | onViewCreated(View, Bundle?) | The view now exists. Bind views, set up your RecyclerView adapter, attach click listeners, and start observing LiveData/Flow using viewLifecycleOwner. |
| 5 | onViewStateRestored(Bundle?) | Any saved view state (scroll position, checkbox state) has been restored. Rarely overridden directly. |
| 6 | onStart() | Fragment becomes visible to the user. Register listeners that only make sense while visible. |
| 7 | onResume() | Fragment is in the foreground and interactive. This is where camera/sensor listeners commonly get re-registered. |
| 8 | onPause() | The user is leaving. Commit lightweight changes here (this must be fast — no disk or network I/O). |
| 9 | onStop() | Fragment is no longer visible. Unregister listeners you added in onStart(). |
| 10 | onDestroyView() | The view hierarchy is being torn down. Clear all view references (binding = null), stop adapters, and cancel anything scoped to viewLifecycleOwner. This is the single most important callback for avoiding memory leaks. |
| 11 | onDestroy() | Final cleanup of fragment-level (not view-level) state. |
| 12 | onDetach() | Fragment is disassociated from its host. Release any reference you stored in onAttach(). |
What happened to onActivityCreated()? It is deprecated as of Fragment 1.3 and should not be used in new code. It used to fire after the host activity's onCreate() completed, but that timing guarantee is redundant now — move that logic into onViewCreated() (for view setup) or onStart() (for logic that genuinely needs the activity to be ready).
Fragment Lifecycle vs. Activity Lifecycle
An Activity's lifecycle is a single, flat sequence: onCreate → onStart → onResume → onPause → onStop → onDestroy. A Fragment's lifecycle wraps around that sequence and adds view-specific callbacks in the middle, because a fragment's view can be destroyed and recreated (for example, when it's on the back stack) independently of the fragment object itself.
Because a fragment is always hosted, its lifecycle is capped by its host: when the host activity is paused, every fragment inside it is paused too; when the activity is destroyed, so are its fragments. This is why a fragment retained across a configuration change (or one placed on the back stack) can have its view destroyed and recreated multiple times while the fragment instance itself survives — something that simply cannot happen with an activity.
The Fragment View Lifecycle (and Why It Matters)
This is the part most tutorials skip, and it is the source of most real-world Fragment bugs. Every AndroidX fragment actually exposes two lifecycle owners:
fragment.lifecycle— tied to the fragment object itself, fromonAttach()toonDetach().fragment.viewLifecycleOwner— tied to the fragment's view, from just afteronCreateView()toonDestroyView().
A fragment can survive on the back stack with its view completely destroyed. If you observe a LiveData or collect a Flow using the fragment's own lifecycle instead of viewLifecycleOwner, that observer keeps running (and can crash trying to update a view that no longer exists) even after onDestroyView() has fired. Always scope UI-bound observers to viewLifecycleOwner, not to the fragment itself:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Correct: tied to the VIEW's lifecycle, cancelled automatically
// at onDestroyView() so it can never touch a dead view.
viewModel.uiState.observe(viewLifecycleOwner) { state ->
binding.titleText.text = state.title
}
}
onCreateView() vs. onViewCreated()
onCreateView() has exactly one job: inflate a layout and return the root View. onViewCreated() runs immediately after, and only if onCreateView() returned a non-null view — this is where you should do everything else: bind views, wire up a RecyclerView adapter, attach click listeners, and start observing state. Keeping inflation and setup in separate methods is what AndroidX itself recommends, and it is also why the modern Fragment(@LayoutRes contentLayoutId: Int) constructor exists — it lets you skip overriding onCreateView() entirely for simple layouts.
Building a Fragment the Modern Way (AndroidX + Kotlin)
Fragments are embedded into a host layout using a FragmentContainerView (the modern, safer replacement for the old <fragment> tag), then added, replaced, or removed at runtime through the FragmentManager.
1. Add the dependency (in build.gradle.kts):
dependencies {
implementation("androidx.fragment:fragment-ktx:1.8.2")
}
2. Host container in the activity layout — activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/swapButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Swap Fragment" />
</LinearLayout>
3. A modern Fragment class — HomeFragment.kt, using view binding and logging every lifecycle callback so you can watch the order for yourself in Logcat:
package com.example.fraglifecycle
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.fraglifecycle.databinding.FragmentHomeBinding
class HomeFragment : Fragment(R.layout.fragment_home) {
// Backing property pattern: only valid between onCreateView and onDestroyView
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onAttach(context: Context) {
super.onAttach(context)
Log.d("Lifecycle", "onAttach")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("Lifecycle", "onCreate")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
Log.d("Lifecycle", "onCreateView")
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d("Lifecycle", "onViewCreated")
binding.titleText.text = "Hello from HomeFragment"
}
override fun onStart() {
super.onStart()
Log.d("Lifecycle", "onStart")
}
override fun onResume() {
super.onResume()
Log.d("Lifecycle", "onResume")
}
override fun onPause() {
super.onPause()
Log.d("Lifecycle", "onPause")
}
override fun onStop() {
super.onStop()
Log.d("Lifecycle", "onStop")
}
override fun onDestroyView() {
super.onDestroyView()
Log.d("Lifecycle", "onDestroyView")
_binding = null // prevent the memory leak
}
override fun onDestroy() {
super.onDestroy()
Log.d("Lifecycle", "onDestroy")
}
override fun onDetach() {
super.onDetach()
Log.d("Lifecycle", "onDetach")
}
}
4. Add, replace, and remove fragments from the activity — MainActivity.kt:
package com.example.fraglifecycle
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.commit
import com.example.fraglifecycle.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if (savedInstanceState == null) {
supportFragmentManager.commit {
add(R.id.fragment_container, HomeFragment())
}
}
binding.swapButton.setOnClickListener {
supportFragmentManager.commit {
replace(R.id.fragment_container, DetailFragment())
addToBackStack(null) // lets the back button return to HomeFragment
}
}
}
}
The fragment-ktx commit { } extension replaces the older, more verbose pattern of manually calling beginTransaction() and commit() on a FragmentTransaction — it does the same thing with automatic commit and less boilerplate.
Fragment Lifecycle Diagram (Text Version)
Here is the full round trip a fragment takes when it is added, put on the back stack, brought back, and finally destroyed:
Added to FragmentManager
onAttach()
onCreate()
onCreateView()
onViewCreated()
onViewStateRestored()
onStart()
onResume() ← fully visible & interactive (RESUMED)
Pushed to the back stack by a "replace" transaction
onPause()
onStop()
onDestroyView() ← view is gone, fragment instance survives (CREATED)
User presses Back — fragment returns from the back stack
onCreateView() ← view is rebuilt from scratch
onViewCreated()
onViewStateRestored()
onStart()
onResume()
Fragment is finally removed / activity finishes
onPause()
onStop()
onDestroyView()
onDestroy()
onDetach() ← end of lifecycle (DESTROYED)
Notice that a fragment coming back off the back stack does not run onAttach() or onCreate() again — the fragment instance itself was never destroyed, only its view. That is exactly why UI setup belongs in onViewCreated(), not onCreate(): onCreate() only ever runs once per fragment instance, while onCreateView()/onViewCreated() can run multiple times.
Common Mistakes and Gotchas
- Leaking the view binding. If you store
FragmentHomeBindingin a non-nullable property and forget to null it out inonDestroyView(), the fragment's view hierarchy leaks every time it goes on the back stack. Always use a nullable backing property and clear it inonDestroyView(). - Observing LiveData/Flow with the wrong lifecycle owner. Passing
this(the fragment) instead ofviewLifecycleOwnertoobserve()means the observer keeps firing even while the fragment has no view, which can crash the app or silently update nothing. - Doing view setup in
onCreate(). There is no view yet at that point. Layout inflation belongs inonCreateView(); everything else belongs inonViewCreated(). - Calling
getActivity()orrequireContext()afteronDetach(). Once a fragment is detached, its host reference is gone; async callbacks (network responses, coroutines) that fire late must checkisAddedor use lifecycle-aware coroutine scopes (viewLifecycleOwner.lifecycleScope) before touching the host or the view. - Mixing
android.support.v4.app.Fragmentandandroidx.fragment.app.Fragmentin the same project. These are two different classes with two differentFragmentManagerimplementations. If you're maintaining an old project, migrate fully to AndroidX rather than mixing them — Android Studio's Refactor > Migrate to AndroidX does this automatically. - Committing a FragmentTransaction after
onSaveInstanceState(). This throwsIllegalStateException: Can not perform this action after onSaveInstanceState. UsecommitAllowingStateLoss()only as a last resort, or better, guard the transaction so it only runs while the fragment/activity is at leastSTARTED. - Forgetting that fragments can outlive their view across configuration changes. On rotation, if the fragment is retained (which is the default for fragments added through the container), only the view lifecycle restarts —
onCreate()is not called again. State that must survive should live in aViewModel, not in fragment fields set only inonCreate().
FragmentManager and FragmentTransaction, Briefly
Two classes do the heavy lifting behind every fragment change:
FragmentManagertracks the fragments attached to an activity or parent fragment, manages the back stack, and determines the maximum lifecycle state each of its fragments is allowed to reach.FragmentTransactionis a set of operations —add(),replace(),remove(),show(),hide()— that you batch together and commit as one atomic unit. CallingaddToBackStack(null)on a transaction means the system will reverse it (recreating the destroyed view) when the user presses Back.
For simple activity-to-activity flows rather than in-activity fragment swaps, see our guide on how to jump from one activity to another in Kotlin.
Frequently Asked Questions
What is the correct order of Fragment lifecycle callbacks?
The first time a fragment is created and shown, the callbacks fire in this order: onAttach(), onCreate(), onCreateView(), onViewCreated(), onViewStateRestored(), onStart(), onResume(). When the fragment is later removed or backgrounded, they unwind in reverse: onPause(), onStop(), onDestroyView(), onDestroy(), onDetach(). If the fragment returns from the back stack, only the view-related callbacks (onCreateView through onResume) run again — onAttach() and onCreate() do not repeat for the same fragment instance.
What is the difference between onCreateView() and onViewCreated()?
onCreateView() has one job: inflate the fragment's layout and return the root View. onViewCreated() runs immediately after, but only if onCreateView() returned a non-null view, and it is where you should bind views, set up a RecyclerView adapter, attach click listeners, and start observing LiveData or Flow. Keeping inflation separate from setup avoids null-pointer issues and matches what AndroidX's Fragment(@LayoutRes id) constructor is built around.
Why is onActivityCreated() deprecated?
onActivityCreated() was deprecated starting in Fragment 1.3 because its main guarantee — that the host activity's onCreate() has finished — is no longer a useful signal on its own. Move view setup code into onViewCreated() and move logic that needs the activity to be fully ready into onStart(). Projects still using onActivityCreated() should migrate off it; it can be removed in future Fragment library versions.
What is the Fragment view lifecycle and why does it matter?
Every AndroidX fragment has two separate Lifecycle objects: one for the fragment instance (onAttach to onDetach) and one for its view (just after onCreateView to onDestroyView), accessible via viewLifecycleOwner. A fragment can survive on the back stack with its view completely destroyed, so observing LiveData or collecting a Flow using the fragment's own lifecycle instead of viewLifecycleOwner keeps the observer alive after the view is gone — a common source of crashes and memory leaks. Always pass viewLifecycleOwner to observe() calls inside a fragment.
How is the Fragment lifecycle different from the Activity lifecycle?
An Activity has one flat lifecycle: onCreate, onStart, onResume, onPause, onStop, onDestroy. A Fragment's lifecycle wraps around that same sequence but adds separate view-creation and view-destruction callbacks, because a fragment's view can be destroyed and rebuilt (for example, when the fragment sits on the back stack) while the fragment object itself stays alive. A fragment's lifecycle state is also always capped by its host activity's state — when the activity pauses, so do all of its fragments.
Should I use android.support.v4.app.Fragment or androidx.fragment.app.Fragment?
Always use androidx.fragment.app.Fragment. The android.support.v4.app.Fragment class is part of the old Android Support Library, which Google stopped updating after migrating everything to Jetpack AndroidX in 2018. It has no access to newer APIs like fragment-ktx's commit {} builder, the Fragment Result API, or Activity Result APIs. If you're maintaining an old project on the support library, use Android Studio's Refactor > Migrate to AndroidX to update it in one pass.
Why does my Fragment crash with IllegalStateException after onSaveInstanceState?
This happens when a FragmentTransaction is committed after the FragmentManager has already saved its state (for example, in an async callback that resolves after the user backgrounds the app). Guard transactions with a check like isStateSaved on the FragmentManager, move the commit earlier in the lifecycle, or as a last resort use commitAllowingStateLoss() — though that can silently drop the transaction on process death, so it should not be the default fix.
What causes a Fragment memory leak?
The most common cause is holding a strong reference to a view binding or a View past onDestroyView(). Because a fragment instance can outlive its view (for example, while sitting on the back stack), any view reference not cleared in onDestroyView() keeps the entire view hierarchy in memory until the fragment itself is destroyed. The fix is to store view bindings in a nullable backing property and set it to null inside onDestroyView().





