Android Fragments
Android: What are Fragments? (Lifecycle) and Using Fragments within Activities
What are Fragments?
A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities.
Think of a Fragment as a "sub-activity" that has its own lifecycle and can manage its own UI. They were introduced to support more flexible and dynamic UI designs, especially on larger screens like tablets, where you might want to show multiple UI components side-by-side within a single screen.
Fragments must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle. For example, when the activity is paused, all fragments in it are paused. When the activity is destroyed, all fragments are destroyed.
Key Characteristics of Fragments:
- Modularity: Break down your UI into reusable components.
- Reusability: Use the same fragment in different activities.
- Adaptability: Create flexible layouts that adapt to different screen sizes and orientations (e.g., multi-pane layouts on tablets).
- Lifecycle: Fragments have their own lifecycle, closely tied to the host Activity.
- No standalone existence: A Fragment must always be hosted by an Activity.
Fragment Lifecycle
The Fragment lifecycle is more complex than the Activity lifecycle because it's dependent on the host Activity and involves interactions with the Activity's FragmentManager.
Here are the most important callback methods in the Fragment lifecycle:
Fragment Lifecycle Callback Methods:
onAttach(Context context)
- Called when the fragment has been associated with the Activity (the Activity is passed as the
context
). This is the first callback received. onCreate(Bundle savedInstanceState)
- Called to do initial creation of the fragment. This is where you initialize essential components of the fragment (like variables) that you want to retain when the fragment is paused or stopped and then resumed.
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
- Called to have the fragment instantiate its user interface view. This is where you inflate your fragment's layout XML and return the root
View
. If your fragment does not provide a UI, returnnull
. onViewCreated(View view, Bundle savedInstanceState)
- Called immediately after
onCreateView()
has returned, but before any saved state has been restored in the view. This is where you can initialize the views within your fragment's layout (e.g., find views usingfindViewById()
). onViewStateRestored(Bundle savedInstanceState)
- Called when all the saved state of the fragment's view hierarchy has been restored.
onStart()
- Called when the fragment is visible to the user and actively running. Corresponds to the Activity's
onStart()
. onResume()
- Called when the fragment is visible and the user can interact with it. Corresponds to the Activity's
onResume()
. onPause()
- Called when the fragment is no longer in the foreground, though it may still be visible. Corresponds to the Activity's
onPause()
. Save any persistent data here. onStop()
- Called when the fragment is no longer visible to the user. Corresponds to the Activity's
onStop()
. onDestroyView()
- Called when the view previously created by
onCreateView()
has been detached from the fragment. Clean up resources related to the view here. onDestroy()
- Called to do final cleanup of the fragment's state. This is where you release any resources that are held by the fragment.
onDetach()
- Called when the fragment is no longer associated with its Activity. This is the final callback received.
Visual Representation (Simplified):
onAttach() | onCreate() | onCreateView() | onViewCreated() | onViewStateRestored() | onStart() | onResume() <--- Fragment is visible and active | onPause() <--- Fragment is no longer in foreground | onStop() <--- Fragment is no longer visible | onDestroyView() <--- View is destroyed | onDestroy() <--- Fragment is destroyed | onDetach() <--- Fragment is detached from Activity
Using Fragments within Activities
To use a Fragment, you need to embed it within an Activity. You can do this in two main ways:
1. Declaring a Fragment in the Activity's Layout XML
This is the simplest way to add a fragment when its position and behavior are fixed within the Activity's layout.
<!-- res/layout/activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello from Activity!"
android:padding="16dp"/>
<!-- Declare your custom Fragment here -->
<fragment
android:id="@+id/my_fragment"
android:name="com.yourpackage.MyFragment" <!-- Replace with your Fragment's full class name -->
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" /> <!-- Example with weight -->
</LinearLayout>
In your Fragment class (MyFragment.kt
):
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_my, container, false) // Replace with your fragment's layout
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Initialize views here, e.g.:
// val textView: TextView = view.findViewById(R.id.fragment_text_view)
// textView.text = "Hello from Fragment!"
}
// Implement other lifecycle methods as needed
}
Explanation:
- You use the
<fragment>
tag in the Activity's layout XML. - The
android:name
attribute specifies the fully qualified class name of your custom Fragment. - The Activity's
FragmentManager
automatically creates an instance of the Fragment and attaches it to the layout when the Activity is created. - You can access the Fragment from the Activity using
findFragmentById(R.id.my_fragment)
.
2. Adding a Fragment Programmatically using Fragment Transactions
This method is more flexible and allows you to add, remove, replace, and perform other operations on fragments at runtime. This is essential for dynamic UIs, navigation (like tabbed interfaces), and handling different screen sizes.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.FragmentTransaction
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // Your layout should have a container view (e.g., FrameLayout)
// Check if the fragment has already been added (e.g., after rotation)
if (savedInstanceState == null) {
// Create a new instance of your Fragment
val myFragment = MyFragment()
// Get the FragmentManager
val fragmentManager = supportFragmentManager // Use supportFragmentManager for androidx libraries
// Start a FragmentTransaction
val transaction = fragmentManager.beginTransaction()
// Add the fragment to a container view in your layout
// R.id.fragment_container is the ID of a layout (e.g., FrameLayout) in your activity_main.xml
transaction.add(R.id.fragment_container, myFragment)
// Optional: Add the transaction to the back stack (allows the user to press back to the previous state)
// transaction.addToBackStack(null) // Or give it a meaningful name
// Commit the transaction
transaction.commit()
}
}
}
In your Activity's layout XML (activity_main.xml
):
<!-- res/layout/activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello from Activity!"
android:padding="16dp"/>
<!-- This is the container where the Fragment will be placed -->
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Explanation:
- You use
supportFragmentManager
(from the AndroidX library) to manage fragments. - You start a
FragmentTransaction
to perform operations on fragments. add(containerViewId, fragment)
adds the fragment to the specified container view in your layout.replace(containerViewId, fragment)
removes any existing fragment in the container and adds the new one.remove(fragment)
removes a fragment.addToBackStack()
allows the user to navigate back to the previous fragment state using the device's back button.commit()
executes the transaction.
Communication Between Fragments and Activities:
Fragments and their host Activities often need to communicate. The recommended way to do this is through an interface defined in the Fragment and implemented by the Activity.
// In your Fragment class (MyFragment.kt)
class MyFragment : Fragment() {
// Define an interface for communication
interface OnButtonClickListener {
fun onButtonClicked(message: String)
}
private var listener: OnButtonClickListener? = null
// Override onAttach to get a reference to the listener
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnButtonClickListener) {
listener = context
} else {
throw RuntimeException("$context must implement OnButtonClickListener")
}
}
override fun onCreateView(...) {
// ... inflate layout ...
val view = inflater.inflate(R.layout.fragment_my, container, false)
val myButton: Button = view.findViewById(R.id.my_button) // Assuming you have a button
myButton.setOnClickListener {
// Call the interface method to communicate with the Activity
listener?.onButtonClicked("Hello from Fragment!")
}
return view
}
// Override onDetach to release the listener reference
override fun onDetach() {
super.onDetach()
listener = null
}
}
// In your Activity class (MainActivity.kt)
class MainActivity : AppCompatActivity(), MyFragment.OnButtonClickListener { // Implement the interface
override fun onCreate(...) {
// ... setup ...
}
// Implement the interface method
override fun onButtonClicked(message: String) {
// Handle the communication from the Fragment
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
Conclusion:
Fragments are powerful building blocks for creating flexible and reusable UI components in Android. Understanding their lifecycle and how to embed and manage them within Activities using XML declarations or Fragment Transactions is crucial for modern Android development. Using interfaces for communication between fragments and their host activities promotes a clean and maintainable architecture.