Android UI, Activities & Navigation

Fragments

18 min Lesson 6 of 12

Fragments

As your Android app grows beyond a single screen, you quickly discover that one Activity per screen is too coarse-grained. A tablet needs to show a master list next to a detail panel simultaneously, while a phone shows each in turn. Duplicating the layout logic inside two separate Activities is unmaintainable. Android solved this with the Fragment — a self-contained, reusable piece of UI and behavior that lives inside an Activity but manages its own view and its own lifecycle.

What a Fragment Is

A Fragment is a class in androidx.fragment.app that represents a portion of the user interface or behavior inside an Activity. Think of it as a modular sub-screen: it has its own layout XML, its own Java class, and its own lifecycle that runs nested inside the host Activity's lifecycle. A single Activity can host multiple Fragments, swap them in and out, and even reuse the same Fragment class in several Activities.

Why Fragments matter: The modern Android architecture — Jetpack Navigation, ViewPager2, bottom-navigation tabs, master-detail layouts — is entirely built on Fragments. Even if you never directly manage the back-stack yourself, the framework is using Fragments under the hood everywhere.

Creating a Fragment

A Fragment class overrides onCreateView() to inflate its layout, and optionally onViewCreated() to set up views after inflation. You should not find views or set listeners in the constructor — the view does not exist yet at construction time.

// ProfileFragment.java package com.example.myapp; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; public class ProfileFragment extends Fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // Inflate the layout for this Fragment return inflater.inflate(R.layout.fragment_profile, container, false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // Safe to find and configure views here TextView nameView = view.findViewById(R.id.tv_name); nameView.setText("Edrees Salih"); } }

The corresponding layout file res/layout/fragment_profile.xml is an ordinary layout XML with a root element — for example a ConstraintLayout — exactly as you would write for an Activity.

Adding a Fragment to an Activity

Fragments are managed by a FragmentManager. You can either declare a Fragment statically in the Activity's layout XML, or add it dynamically at runtime through a FragmentTransaction. The dynamic approach is almost always what you want because it lets you replace and animate Fragments.

Static declaration (layout XML) — simplest, but not replaceable at runtime:

<!-- res/layout/activity_main.xml --> <androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:name="com.example.myapp.ProfileFragment" android:layout_width="match_parent" android:layout_height="match_parent" />

Dynamic transaction (Java) — the flexible approach:

// MainActivity.java import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // contains an empty FragmentContainerView // Avoid re-adding the Fragment on configuration change (rotation) if (savedInstanceState == null) { FragmentManager fm = getSupportFragmentManager(); FragmentTransaction tx = fm.beginTransaction(); tx.add(R.id.fragment_container, new ProfileFragment(), "profile"); tx.commit(); } } }
Always guard Fragment additions with savedInstanceState == null. When the device rotates, Android recreates the Activity AND automatically restores any Fragments that were already committed to the back-stack. If you unconditionally add the Fragment again, you end up with duplicate Fragment instances layered on top of each other.

The Fragment Lifecycle

The Fragment lifecycle runs alongside the host Activity's lifecycle, but has additional callbacks specific to its relationship with the Activity and its own view:

  1. onAttach(context) — Fragment is attached to the Activity. The Context is now available.
  2. onCreate(savedInstanceState) — Fragment is created. Initialize non-view state here (ViewModels, arguments).
  3. onCreateView() — Inflate and return the Fragment's view hierarchy.
  4. onViewCreated(view, savedInstanceState) — View is ready. Set up listeners, observe LiveData, populate UI here.
  5. onStart() — Fragment becomes visible.
  6. onResume() — Fragment is in the foreground and interactive.
  7. onPause() — Fragment loses focus (another Fragment or Activity comes to the foreground).
  8. onStop() — Fragment is no longer visible.
  9. onDestroyView() — The view hierarchy is being destroyed. Release any references to views here to avoid memory leaks.
  10. onDestroy() — Fragment itself is destroyed.
  11. onDetach() — Fragment is detached from the Activity.
onDestroyView vs onDestroy: A Fragment's view can be destroyed and recreated (for example when it is placed on the back-stack) while the Fragment instance itself stays alive. This is why you must null out any view references in onDestroyView(). ViewBinding makes this easy with the pattern: binding = null; in onDestroyView().

Fragment Arguments — the newInstance Pattern

Never pass data to a Fragment through a constructor with parameters. Android will recreate the Fragment via its no-arg constructor when restoring state, losing any constructor arguments. The correct approach is the newInstance factory pattern: pack data into a Bundle and attach it via setArguments().

public class DetailFragment extends Fragment { private static final String ARG_USER_ID = "user_id"; // Factory method — call this instead of the constructor public static DetailFragment newInstance(int userId) { DetailFragment fragment = new DetailFragment(); Bundle args = new Bundle(); args.putInt(ARG_USER_ID, userId); fragment.setArguments(args); return fragment; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); int userId = requireArguments().getInt(ARG_USER_ID); // Load data for userId ... } }

At the call site in the Activity:

getSupportFragmentManager() .beginTransaction() .replace(R.id.fragment_container, DetailFragment.newInstance(42)) .addToBackStack(null) // pressing Back will pop this Fragment .commit();
Use replace() with addToBackStack() when navigating forward. replace() swaps out the current Fragment; addToBackStack() ensures the system Back button reverses the transaction and restores the previous Fragment, giving users the navigation experience they expect.

When to Use Fragments

  • Multi-pane layouts: Show list + detail side-by-side on tablets; one pane at a time on phones.
  • Tab navigation: Each tab is a Fragment inside a ViewPager2 or a bottom navigation bar.
  • Reusable UI sections: A shared toolbar, a map view, or a comments section that appears on multiple screens.
  • Jetpack Navigation: The Navigation component manages a single-Activity app where every screen is a Fragment destination — this is the recommended modern architecture.

Summary

A Fragment is a modular, reusable UI component that lives inside an Activity. It has its own lifecycle (onCreateView, onViewCreated, onDestroyView being the most important callbacks for UI work), its own layout, and its own state. Data is passed in via the newInstance argument bundle pattern, not constructor arguments. Fragment transactions — add, replace, addToBackStack — are the mechanism by which an Activity composes and navigates between multiple Fragments. In the next lesson you will use the Jetpack Navigation component, which builds on Fragments to give you a complete screen-navigation graph.