Kahibaro
Discord Login Register

12.1 Fragment Basics

Why Fragments Exist

Fragments let you split an activity screen into smaller, reusable parts. Instead of putting all UI and logic inside one activity, you can place parts of the screen into fragments and let the activity host them.

A fragment has its own layout and its own small controller class, but it cannot live on its own. It must always exist inside an activity. This makes fragments useful when you want to reuse the same screen section on a phone and a tablet, or when you want to switch parts of the UI without changing the whole activity.

Fragments are especially important when building apps with multiple panels, navigation components, or when you want to move to more modern, modular architectures. The activity becomes responsible for navigation and high level coordination, and fragments are responsible for displaying specific parts of the UI.

A fragment cannot exist without a host activity. It always depends on an activity for its lifecycle and context.

Fragment vs Activity

An activity is a full screen or window that the Android system manages directly. A fragment is a portion of UI and behavior that lives inside an activity.

The system can start, stop, and destroy activities as separate application entries. Fragments are not started by the system. They are created and attached by the activity, usually through the FragmentManager API.

An activity has its own lifecycle callbacks, like onCreate, onStart, and onResume. A fragment also has callbacks, but they are tied to the activity lifecycle. When the activity is created or destroyed, the fragment follows along.

The key difference is responsibility. Use an activity to represent a full-screen task or a major navigation destination. Use fragments to break that task into smaller, reusable UI blocks.

The Fragment Class

A fragment is represented by a class that usually extends Fragment from the AndroidX library. This class manages its own view hierarchy and its own logic.

A typical fragment class includes a primary constructor that calls the Fragment base class, an optional onCreate method for initial setup that does not involve the view, and an onCreateView method to inflate and return the fragment's layout.

In most modern projects you will use either the basic Fragment class or a version that supports view binding. The idea always remains the same: the fragment's job is to create and manage its view, then react to lifecycle events provided by the system through the host activity.

Creating a Simple Fragment

To create a fragment in Android Studio, you typically add a new Kotlin class that extends Fragment. You also create a separate XML layout file for the fragment's UI.

A very simple fragment might look like this:

class HelloFragment : Fragment(R.layout.fragment_hello)

Here, the fragment uses a layout resource directly in the constructor. When Android needs the fragment's view, it inflates fragment_hello.xml and attaches it to the fragment automatically.

If you need more control over how the layout is created, you can override onCreateView:

class HelloFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_hello, container, false)
    }
}

This method receives a LayoutInflater that knows how to load XML layouts into actual view objects, a container that will host the fragment's view, and an optional savedInstanceState bundle for restoring state.

Fragment Layout Files

Each fragment typically has its own layout file in the res/layout directory. This file describes the UI that the fragment will manage.

The layout is similar to an activity layout, since both use the same view system. The difference is that a fragment's root view is not the entire screen. It is just the part of the screen that the activity will reserve for the fragment.

For example, fragment_hello.xml could look like this:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/hello_fragment_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/hello_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello from Fragment" />
</FrameLayout>

The root element is often a FrameLayout, LinearLayout, or ConstraintLayout, which will be inserted into the activity's view hierarchy at the position reserved for the fragment.

Adding a Fragment to an Activity Layout

There are two main ways to place a fragment inside an activity. The first is static, where you declare the fragment directly in the activity's XML layout.

With static addition, the fragment is defined by the layout file and exists as soon as the activity is created. The activity layout needs a <fragment> tag, or for more modern patterns, a FragmentContainerView that hosts the fragment.

An example with FragmentContainerView looks like this:

<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/hello_fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.example.app.HelloFragment" />

The android:name attribute tells Android which fragment class to create and attach to this container. The activity does not need extra code to show this fragment, because the system inflates it automatically when the activity layout is loaded.

Adding a Fragment Dynamically

The second way is dynamic addition. In this case, the activity decides at runtime which fragment to create and where to place it. This is useful when you want to change fragments based on user actions or screen size.

First, the activity layout contains a simple container view, often a FrameLayout, which will hold the fragment's view:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Next, the activity uses the supportFragmentManager to start a fragment transaction and add the fragment:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (savedInstanceState == null) {
            val fragment = HelloFragment()
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit()
        }
    }
}

The check for savedInstanceState == null makes sure that the fragment is only added the first time the activity is created, not when it is recreated after a configuration change.

FragmentManager and Transactions

When you work with fragments dynamically, the main tool is the FragmentManager. This object tracks which fragments are attached to the activity and controls how they are added, removed, or replaced.

To make a change, you start a fragment transaction. The transaction describes all fragment operations that should happen together, such as adding a fragment, removing another, or replacing one with another.

A simple transaction to replace the current fragment might look like this:

supportFragmentManager.beginTransaction()
    .replace(R.id.fragment_container, HelloFragment())
    .commit()

The replace call removes any existing fragment in the specified container and adds the new one. When you call commit, the transaction is scheduled and applied by the system.

If you want the user to be able to go back to the previous fragment using the back button, you can add the transaction to the back stack. That concept is discussed separately, but the basic call looks like this:

supportFragmentManager.beginTransaction()
    .replace(R.id.fragment_container, HelloFragment())
    .addToBackStack(null)
    .commit()

Using Fragment Arguments

When you need to pass simple data into a fragment when you create it, you can use a Bundle of arguments. This keeps fragment construction predictable and avoids issues when Android recreates the fragment.

The usual pattern is to define a newInstance function in the fragment class. This function builds the fragment and attaches the arguments using the arguments property.

A basic example looks like this:

class MessageFragment : Fragment(R.layout.fragment_message) {
    companion object {
        private const val ARG_MESSAGE = "arg_message"
        fun newInstance(message: String): MessageFragment {
            val fragment = MessageFragment()
            val bundle = Bundle()
            bundle.putString(ARG_MESSAGE, message)
            fragment.arguments = bundle
            return fragment
        }
    }
    // Later in the fragment, you can read the argument
    // val message = arguments?.getString(ARG_MESSAGE)
}

The important idea is that the system can recreate the fragment using the stored arguments. You do not rely on custom constructors, because the framework will always call the default constructor when it restores fragments.

Do not define custom constructors with parameters for fragments. Always use a no argument constructor and a Bundle of arguments to pass data into a fragment.

Fragment Lifecycle Overview

A fragment has its own lifecycle that is connected to, but not identical with, the activity lifecycle. The system calls fragment lifecycle methods as it attaches the fragment to the activity, creates the fragment's view, and later destroys it.

Some lifecycle events relate to the fragment object itself, such as onCreate and onDestroy. Others relate specifically to the fragment's view, such as onCreateView and onDestroyView. This separation is important because the fragment object can survive after its view has been destroyed.

In basic usage, you often put non view initialization logic in onCreate, inflate the UI in onCreateView, and then set up listeners and view related work in onViewCreated. Cleanup related to the view should go into onDestroyView. Deeper details of fragment lifecycle and callbacks are covered separately, but at this stage it is enough to understand that the fragment lifecycle works in close connection with the host activity lifecycle.

Common Use Cases for Fragments

Fragments become more useful as your app grows. One common case is building a master detail layout where a list of items appears on the left and a detail view appears on the right on tablets. Each side can be its own fragment, and the activity can show either both or only one depending on screen size.

Another frequent use is modular navigation. Different steps in a sign up flow, different tabs inside a single activity, or different sections of a settings screen can each be their own fragment. The activity stays simple and focuses on navigation between fragments, while each fragment focuses on a specific piece of functionality.

Fragments also work closely with many modern Android libraries. For example, navigation components often use fragments as destinations, and ViewModel and LiveData integrate easily with fragment lifecycles. Understanding basic fragment structure makes it easier to use these components later.

Good Practices With Fragments

To avoid fragile designs, treat fragments as self contained units of UI, not as direct replacements for activities. The activity should coordinate navigation and high level behavior, and fragments should manage their own views and local logic.

It is usually better to avoid direct references between fragments. Instead, use the activity or a shared ViewModel as a bridge. This keeps your architecture more flexible when you modify or replace fragments.

You should also be careful not to keep references to views after onDestroyView is called. The fragment object can remain in memory, but its views no longer exist. Any operations that touch views should only happen while the view is alive.

Finally, remember that fragments are created and destroyed by the system as needed. Use arguments and saved state properly, and keep long lived state in places that survive configuration changes, such as ViewModels.

Views: 1

Comments

Please login to add a comment.

Don't have an account? Register now!