Fragments

A Fragment is a reusable, self-contained piece of UI that lives inside an Activity rather than standing on its own. It has its own layout, its own logic, and — as you'll see below — its own small lifecycle, but it can never appear on screen without a host Activity to hold it.

Why Reach for a Fragment at All?

If an Activity can already show a layout, why add another layer? Two reasons come up constantly in real apps:

  • Adapting across screen sizes. On a phone, TrailLog might show the trail list full-screen; on a tablet, it could show the list and a selected trail's detail side by side. Splitting each piece into its own Fragment makes that kind of layout swap realistic without duplicating logic.
  • Breaking up a complex screen. A single Activity juggling a map, a trail list, and a filter panel all at once gets messy fast. Three focused Fragments, each responsible for one piece, are far easier to reason about and reuse.

Worked Example: TrailListFragment

Instead of putting the trail list directly in an Activity, TrailLog defines a TrailListFragment that owns that one job: show the list of logged trails. A Fragment subclasses Fragment and overrides onCreateView() to inflate its own layout, separate from whatever layout its host Activity uses.

class TrailListFragment : Fragment() {

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // safe to look up views and bind the trail list adapter here
    }
}

Notice that onCreateView() only inflates the layout and returns it — it deliberately doesn't touch any views yet. That's because the view hierarchy isn't fully ready until onViewCreated() runs immediately afterward, which is the right place to bind an adapter or look up individual views.

A Fragment's Own Mini-Lifecycle

A Fragment is attached to a host Activity, so it rides along with that Activity's lifecycle, but it also has extra callbacks of its own that track the Fragment's relationship to its host and its view:

onAttach() ──▶ the Fragment is now attached to its host Activity onCreateView() ──▶ inflate and return this Fragment's own layout onViewCreated() ──▶ the view hierarchy exists — safe to bind views, set up adapters [ Fragment visible, following its host Activity's lifecycle ] onDestroyView() ──▶ this Fragment's view is being torn down (the Fragment instance itself may live on)

onDestroyView() deserves a second look: the Fragment object can outlive its view, for example when it's placed on the back stack. Any reference to a view or a View Binding instance should be cleared here, or you risk holding on to a view that no longer exists.

Hosting a Fragment with FragmentContainerView

A Fragment needs somewhere to live. The modern way to declare that spot in an Activity's XML layout is FragmentContainerView:

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/trail_fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.codingwithsonu.traillog.TrailListFragment" />

With android:name set directly on the container, the system instantiates TrailListFragment and places it there automatically as soon as the Activity's layout is inflated — no extra Kotlin code required for this simple, static case.

Adding Fragments Programmatically: FragmentTransaction

When a Fragment needs to be swapped in at runtime — say, replacing the trail list with a detail view when the user taps an item — you reach for a FragmentTransaction, obtained from a FragmentManager:

childFragmentManager.beginTransaction()
    .replace(R.id.trail_fragment_container, TrailDetailFragment())
    .addToBackStack(null)
    .commit()

childFragmentManager is used when one Fragment hosts another Fragment inside itself; an Activity hosting a top-level Fragment instead uses supportFragmentManager. This API is flexible but entirely manual — you're responsible for the back stack, arguments, and transaction timing yourself. The next lesson, Navigation Component, covers how the Navigation library handles most of this automatically in a modern app, so you rarely write raw FragmentTransaction code by hand once it's set up.

Passing Data Into a Fragment: The newInstance() Pattern

A Fragment needs a no-argument constructor, since the system may recreate it on its own (say, after a configuration change) without knowing what arguments you'd originally passed. The established Kotlin idiom for supplying startup data anyway is a companion-object factory function conventionally called newInstance(), which packages the data into the Fragment's arguments Bundle:

class TrailDetailFragment : Fragment() {

    companion object {
        private const val ARG_TRAIL_ID = "trail_id"

        fun newInstance(trailId: Int): TrailDetailFragment {
            val fragment = TrailDetailFragment()
            fragment.arguments = Bundle().apply {
                putInt(ARG_TRAIL_ID, trailId)
            }
            return fragment
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val trailId = arguments?.getInt(ARG_TRAIL_ID) ?: -1
        // use trailId to look up and render the right Trail
        return inflater.inflate(R.layout.fragment_trail_detail, container, false)
    }
}

Callers create the Fragment through TrailDetailFragment.newInstance(trail.id) instead of the plain constructor, which keeps the "how do I configure this Fragment's starting data" question answered in exactly one place.

Note: As with every example in this track, this is correct, static Kotlin/Android code — this website's own project doesn't run a live Android Studio build, so nothing here executes on the page.
Coming up: Manually wiring FragmentTransactions and back-stack calls gets tedious fast. Navigation Component shows how Jetpack's Navigation library manages Fragment swapping, arguments, and the back stack for you.