Fragment Lifecycle Overview
The Fragment Lifecycle
A Fragment is a reusable portion of UI that lives inside an Activity. Its lifecycle is tied to the host Activity — fragments can be added, removed, replaced, and reordered while the activity is running.
Lifecycle Callbacks
class HomeFragment : Fragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
// Fragment attached to activity — validate host implements required interfaces
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Fragment created — initialize non-UI components
// Retain instance here across configuration changes
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
// Inflate the layout — this is where you set up the view hierarchy
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// View created — safe to access view references here
// Use viewLifecycleOwner for LiveData observation
}
override fun onStart() {
super.onStart()
// Fragment visible
}
override fun onResume() {
super.onResume()
// Fragment in foreground
}
override fun onPause() {
super.onPause()
// Fragment partially obscured
}
override fun onStop() {
super.onStop()
// Fragment not visible
}
override fun onDestroyView() {
super.onDestroyView()
// View destroyed — null out view references to prevent leaks
}
override fun onDestroy() {
super.onDestroy()
// Fragment destroyed — clean up non-view resources
}
override fun onDetach() {
super.onDetach()
// Fragment detached from activity
}
}
Key Differences from Activity Lifecycle
Fragments have three additional callbacks compared to Activities:
onAttach— connected to the host activityonCreateView— inflating the UI layoutonDestroyView— view destroyed but fragment still alive
And one extra state: onViewCreated — called after onCreateView when the view is ready.
Configuration Changes
Fragments have a special behavior during configuration changes: onDestroyView → onCreateView → onViewCreated fires (view is recreated), but the fragment instance itself survives. The onDestroy → onCreate pair only fires when the fragment is truly removed.
Use viewLifecycleOwner instead of this when observing LiveData to avoid observing stale view references after configuration changes.
Fragment Communication
Fragment Communication
Fragments should never directly communicate with each other. Instead, communicate through the host Activity or use shared ViewModel.
Option 1: Shared ViewModel
class SharedViewModel : ViewModel() {
private val _selectedItem = MutableLiveData<String>()
val selectedItem: LiveData<String> = _selectedItem
fun selectItem(item: String) {
_selectedItem.value = item
}
}
class ListFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
sharedViewModel.selectedItem.observe(viewLifecycleOwner) { item ->
// Respond to selection from detail fragment
}
}
}
class DetailFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
fun onItemSelected(item: String) {
sharedViewModel.selectItem(item)
}
}
The ViewModel is scoped to the Activity, so both fragments share the same instance.
Option 2: Fragment Result API
For one-shot communication (not continuous observation):
// Sending fragment
class SenderFragment : Fragment() {
fun sendData() {
setFragmentResult("requestKey", bundleOf("dataKey" to "Hello"))
}
}
// Receiving fragment
class ReceiverFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("requestKey") { _, bundle ->
val data = bundle.getString("dataKey")
// Handle data
}
}
}
Option 3: Interface Callbacks (Old Pattern)
class ListFragment : Fragment() {
private var listener: OnItemSelectedListener? = null
interface OnItemSelectedListener {
fun onItemSelected(item: String)
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnItemSelectedListener) {
listener = context
} else {
throw RuntimeException("${context} must implement OnItemSelectedListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
}
This pattern is fragile — the host activity must implement the interface. Prefer shared ViewModel for modern apps.
Fragment Transactions
Fragment Transactions
FragmentManager manages adding, replacing, and removing fragments. All changes happen through transactions:
Basic Transactions
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, HomeFragment())
.commit()
}
}
}
Always check savedInstanceState == null before committing the initial fragment — otherwise you'll get duplicate fragments after configuration changes.
Adding to Back Stack
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, DetailFragment())
.addToBackStack(null) // User can press Back to return
.commit()
Fragment Reuse and Arguments
Don't create new fragment instances — use the newInstance pattern with arguments:
class DetailFragment : Fragment() {
companion object {
fun newInstance(itemId: String): DetailFragment {
return DetailFragment().apply {
arguments = bundleOf("itemId" to itemId)
}
}
}
private val itemId: String by lazy {
requireArguments().getString("itemId", "")
}
}
// Usage
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, DetailFragment.newInstance("123"))
.addToBackStack(null)
.commit()
View Binding in Fragments
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // Prevent memory leak
}
}
Null out the binding in onDestroyView to prevent memory leaks — the view is destroyed but the fragment may still be alive.
Quiz
1. Which callback is unique to Fragments (not in Activity)?
2. Why should you use viewLifecycleOwner for observing LiveData in fragments?
3. What is the correct way to pass data between fragments?
4. Why must you null out view binding in onDestroyView?
Flashcards
Question
What are the three additional lifecycle callbacks in Fragments compared to Activities?
Click to reveal answer
Answer
onAttach (connected to activity), onCreateView (inflate layout), onDestroyView (view destroyed).
Question
What is the recommended way for fragments to communicate?
Click to reveal answer
Answer
Use a shared ViewModel (scoped to the activity) or the Fragment Result API. Avoid direct fragment-to-fragment communication.
Question
Why check savedInstanceState == null before adding the first fragment?
Click to reveal answer
Answer
Otherwise the fragment is added again after configuration changes, causing duplicates.
Question
What pattern should you use to pass data to a fragment?
Click to reveal answer
Answer
Use arguments Bundle via a newInstance companion function. Never pass data through constructors.
Revision Notes
Key Takeaways
- 1. Fragment lifecycle is more complex than Activity lifecycle with additional view-specific callbacks
- 2. viewLifecycleOwner prevents stale view references after configuration changes
- 3. Shared ViewModel is the modern way for fragment-to-fragment communication
- 4. Always null out view binding in onDestroyView to prevent memory leaks
- 5. Check savedInstanceState == null to avoid duplicate fragments on rotation
Interview Tips
- • Know the complete fragment lifecycle and how it differs from Activity
- • Explain why viewLifecycleOwner is necessary for LiveData observation
- • Discuss the trade-offs between shared ViewModel, Fragment Result API, and interface callbacks
- • Be ready to explain how to handle fragment back stack properly
Cheat Sheet
Fragment Lifecycle Cheat Sheet
Callbacks (in order):
onAttach— connected to activityonCreate— initialize non-UIonCreateView— inflate layoutonViewCreated— view readyonStart— visibleonResume— foregroundonPause— partially obscuredonStop— not visibleonDestroyView— view destroyedonDestroy— fragment destroyedonDetach— disconnected
Key Rules:
- Use
viewLifecycleOwnerfor LiveData - Null out binding in onDestroyView
- Check savedInstanceState == null for initial fragment
- Use newInstance pattern with arguments
- Never communicate directly between fragments