Kotlin Fragment Lifecycle
Introduction
Fragments are modular sections of an Android application's user interface that allow you to build flexible and reusable UI components. Understanding the Fragment lifecycle is crucial for developing robust Android applications with Kotlin, as it helps you manage resources efficiently and avoid common pitfalls like memory leaks and crashes.
In this guide, we'll explore the complete Fragment lifecycle, understand each callback method, and learn how to properly implement them in your Kotlin Android applications. Whether you're building a simple single-screen app or a complex multi-pane interface, mastering Fragment lifecycle will significantly improve your development skills.
What are Fragments?
Before diving into the lifecycle, let's clarify what Fragments are:
Fragments represent a portion of the user interface within an Activity. They have their own layout, their own behavior, and their own lifecycle. You can think of them as "sub-activities" that can be:
- Combined to build a multi-pane UI
- Reused across different activities
- Added, removed, or replaced dynamically while the activity is running
- Stacked in a back stack for navigation
Fragment Lifecycle Overview
The Fragment lifecycle is closely related to the Activity lifecycle but has additional callback methods to support its unique features. Here's a visual overview of the Fragment lifecycle phases:
- Initialization: The Fragment is instantiated
- Creation: The Fragment is being created
- View Creation: The Fragment's view is being created and initialized
- Activity Creation Complete: The Fragment is active within the Activity
- Start & Resume: The Fragment becomes visible and active
- Pause & Stop: The Fragment becomes inactive and potentially invisible
- View Destruction: The Fragment's view is being destroyed
- Destruction: The Fragment itself is being destroyed
Detailed Fragment Lifecycle Methods
Let's explore each lifecycle callback method in detail:
1. onAttach()
override fun onAttach(context: Context) {
super.onAttach(context)
Log.d("FragmentLifecycle", "onAttach called")
// Initialize resources that require context
}
This is the first callback in the Fragment lifecycle. It's called when the Fragment is first attached to its host Activity. You receive the Context of the Activity it's attached to.
2. onCreate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("FragmentLifecycle", "onCreate called")
// Initialize non-view related components
// Restore any saved state if needed
setRetainInstance(true) // Optional: to retain instance across config changes
}
Called to initialize the Fragment. At this point, the Fragment is not yet associated with its layout. Here you can initialize components that don't require a view.
3. onCreateView()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("FragmentLifecycle", "onCreateView called")
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_example, container, false)
}
This is where you inflate the Fragment's layout and return the root View. This method is called between onCreate()
and onViewCreated()
.
4. onViewCreated()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d("FragmentLifecycle", "onViewCreated called")
// Initialize UI components
// Setup click listeners, recycler views, etc.
val textView = view.findViewById<TextView>(R.id.text_view)
textView.text = "Fragment is created!"
}
Called immediately after onCreateView()
. This is the preferred place to initialize your UI components like finding views and setting up listeners.
5. onViewStateRestored()
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
Log.d("FragmentLifecycle", "onViewStateRestored called")
// Restore state of views (like EditText content)
}
Called when all saved state has been restored to the views of the fragment.
6. onStart()
override fun onStart() {
super.onStart()
Log.d("FragmentLifecycle", "onStart called")
// Fragment is visible
}
Called when the Fragment is visible to the user.
7. onResume()
override fun onResume() {
super.onResume()
Log.d("FragmentLifecycle", "onResume called")
// Fragment is active and interactable
// Start animations, update UI with live data
}
Called when the Fragment is ready to interact with the user. This is a good place to start animations or refresh data.
8. onPause()
override fun onPause() {
super.onPause()
Log.d("FragmentLifecycle", "onPause called")
// Fragment is no longer interactable
// Pause animations, save user input
}
Called when the user is leaving the fragment. Part of the activity may still be visible. Use this to pause animations or commit unsaved changes.
9. onStop()
override fun onStop() {
super.onStop()
Log.d("FragmentLifecycle", "onStop called")
// Fragment is no longer visible
// Stop updates, release resources that are not needed
}
Called when the Fragment is no longer visible to the user.
10. onDestroyView()
override fun onDestroyView() {
super.onDestroyView()
Log.d("FragmentLifecycle", "onDestroyView called")
// Clean up resources associated with the view
// Nullify view references to prevent memory leaks
}
Called when the view hierarchy associated with the fragment is being removed. Clean up resources and references to view objects here.
11. onDestroy()
override fun onDestroy() {
super.onDestroy()
Log.d("FragmentLifecycle", "onDestroy called")
// Clean up any remaining resources
// Fragment is being destroyed, not guaranteed to be called
}
Called when the Fragment is being destroyed. Note that this is not guaranteed to be called, so critical cleanup should happen in onDestroyView()
or onStop()
.
12. onDetach()
override fun onDetach() {
super.onDetach()
Log.d("FragmentLifecycle", "onDetach called")
// Fragment is detached from its activity
// Clear any references to the activity
}
The final call in the fragment lifecycle. The Fragment is no longer attached to its Activity.
Practical Example: A Complete Fragment
Let's put everything together in a practical example of a Fragment that displays a counter which can be incremented by a button:
class CounterFragment : Fragment() {
private var counter = 0
private var counterTextView: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Restore state if available
savedInstanceState?.let {
counter = it.getInt("counter_value", 0)
}
Log.d("CounterFragment", "onCreate: Counter initialized to $counter")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("CounterFragment", "onCreateView called")
return inflater.inflate(R.layout.fragment_counter, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
counterTextView = view.findViewById(R.id.counter_text)
val incrementButton = view.findViewById<Button>(R.id.increment_button)
// Update the UI to show the current counter value
updateCounterDisplay()
// Set click listener
incrementButton.setOnClickListener {
counter++
updateCounterDisplay()
Log.d("CounterFragment", "Counter incremented to $counter")
}
Log.d("CounterFragment", "onViewCreated: UI components initialized")
}
private fun updateCounterDisplay() {
counterTextView?.text = "Count: $counter"
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// Save the counter value
outState.putInt("counter_value", counter)
Log.d("CounterFragment", "onSaveInstanceState: Saved counter = $counter")
}
override fun onDestroyView() {
super.onDestroyView()
// Clean up references
counterTextView = null
Log.d("CounterFragment", "onDestroyView: Cleaned up view references")
}
}
The corresponding layout file (fragment_counter.xml
) might look like:
<?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"
android:gravity="center">
<TextView
android:id="@+id/counter_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Count: 0"
android:textSize="24sp"
android:layout_margin="16dp" />
<Button
android:id="@+id/increment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Increment" />
</LinearLayout>
To use this fragment in an activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
// Only add the fragment when the activity is created for the first time
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, CounterFragment())
.commit()
}
}
}
Fragment Configuration Changes
When a configuration change occurs (like screen rotation), by default, the Activity and its Fragments are destroyed and recreated. To handle this properly:
- Save state in
onSaveInstanceState()
- Restore state in
onCreate()
oronViewCreated()
Alternatively, you can retain the Fragment instance across configuration changes:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true // Deprecated in newer Android versions
}
In newer Android development, it's recommended to use ViewModel with SavedStateHandle instead of retainInstance = true
.
Fragment Lifecycles with ViewPager and Navigation Component
When using fragments with components like ViewPager or the Navigation Component, there are additional lifecycle considerations:
- ViewPager: Fragments may be in a "created" state but not visible when they are adjacent to the current page
- Navigation Component: The Navigation component manages the back stack and fragment transactions for you
Best Practices
- Avoid Activity References: Don't hold direct references to the host Activity beyond
onDetach()
- Clean Up Resources: Release resources in the appropriate lifecycle method
- Use ViewModel: Separate UI data from UI controllers to survive configuration changes
- Prefer
onViewCreated()
overonCreateView()
: For view initialization - Nullify View References: In
onDestroyView()
to prevent memory leaks
Common Pitfalls
- Memory Leaks: Forgetting to clean up resources or holding references to destroyed views
- Activity Casts: Unsafe casting of context to specific Activity types
- Illegal State: Performing fragment transactions after
onSaveInstanceState()
- UI Updates in Wrong Method: Trying to update views before
onCreateView()
or afteronDestroyView()
Summary
The Fragment lifecycle is more complex than the Activity lifecycle but understanding it is essential for building flexible and efficient Android applications. The key takeaways are:
- Fragments have their own lifecycle that is coordinated with the Activity's lifecycle
- The View lifecycle of a Fragment is separate from the Fragment's own lifecycle
- Proper resource management requires understanding when to initialize and clean up
- State saving and restoration is critical for handling configuration changes
By following the best practices and avoiding common pitfalls, you'll be able to create robust Fragment-based UIs that provide a seamless user experience across different device configurations and states.
Additional Resources
- Android Developer Documentation: Fragment
- Android Developer Documentation: Handling Lifecycles
- Jetpack Navigation Component
Exercises
- Create a Fragment that displays a timer that continues counting even after configuration changes
- Implement a Fragment with form fields that preserve user input across configuration changes
- Build a Fragment that loads data from a network source and properly handles lifecycle events (loading state, error state, etc.)
- Create two fragments that communicate with each other using a shared ViewModel
- Implement a navigation flow between multiple fragments using the Navigation Component
By completing these exercises, you'll gain practical experience with Fragment lifecycle management and be better prepared to build complex Android applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)