Kahibaro
Discord Login Register

12.3 Fragment Transactions

Understanding Fragment Transactions

Fragment transactions are the way you tell Android to add, remove, show, hide, or replace fragments inside an activity. Every visual change that involves fragments goes through a transaction. In this chapter you will focus on what a transaction is, how to build one, and how the fragment back stack affects navigation.

The FragmentManager and FragmentTransaction

Every activity that uses fragments works with a FragmentManager. The FragmentManager knows which fragments are currently attached to the activity, which ones are visible, and manages the back stack of fragments.

To change fragments, you ask the FragmentManager for a FragmentTransaction. A FragmentTransaction is an object that collects all fragment operations you want to perform at once. You can think of it as a small script of changes that are applied together.

You obtain a transaction with code like this inside an activity that uses the default fragment library:

val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()

After this, you call transaction methods such as add, replace, or remove. When you are done, you call commit() to apply all the changes.

Basic Operations: add, replace, remove

A fragment transaction supports several operations. The most common ones are add, replace, and remove. All of them work with a container in your activity layout, usually a FrameLayout or similar, and an instance of a fragment.

To add a fragment into a container you use:

val fragment = ExampleFragment()
supportFragmentManager.beginTransaction()
    .add(R.id.fragment_container, fragment, "EXAMPLE_TAG")
    .commit()

In this example R.id.fragment_container is the ID of the view in the activity layout that will host the fragment. The string "EXAMPLE_TAG" is an optional tag you can use later to find this fragment.

If you want to replace whatever fragment is currently in the container with a new one, you use replace:

val newFragment = DetailsFragment()
supportFragmentManager.beginTransaction()
    .replace(R.id.fragment_container, newFragment, "DETAILS_TAG")
    .commit()

replace internally removes the current fragment from that container and adds the new one. This is often used when navigating from a list fragment to a detail fragment.

To remove a specific fragment that is currently added, you call remove:

val fragmentToRemove =
    supportFragmentManager.findFragmentByTag("EXAMPLE_TAG")
if (fragmentToRemove != null) {
    supportFragmentManager.beginTransaction()
        .remove(fragmentToRemove)
        .commit()
}

Removing a fragment detaches its view from the container and moves it through its lifecycle accordingly. You will normally use remove for fragments that you no longer want to show or reuse.

Important rule: add, replace, and remove do not execute immediately. They only take effect after you call commit() on the transaction.

Committing a Transaction

When you call beginTransaction() you start building a transaction. You can chain multiple operations on this transaction before you commit it. For example, you can add two fragments in a single transaction so they appear together.

supportFragmentManager.beginTransaction()
    .add(R.id.top_container, HeaderFragment())
    .add(R.id.bottom_container, ContentFragment())
    .commit()

commit() schedules the transaction to be executed on the main thread. The changes typically happen shortly after, but not exactly at the moment you call commit(). This delayed execution is important for keeping the UI responsive.

There is a variation called commitNow() that executes the transaction immediately, but it should be used carefully and only when you really need synchronous execution, because it can lead to complicated ordering issues inside your UI.

Another variation is commitAllowingStateLoss(). This allows the transaction to be committed even if the activity state has already been saved. You usually want to avoid this unless you fully understand the implications, because it can lead to situations where the UI state and the saved state are out of sync.

Important rule: Prefer commit() over commitAllowingStateLoss(). Use commitAllowingStateLoss() only if you accept that some UI changes might not be restored after process death.

Adding Transactions to the Back Stack

The fragment back stack works similarly to the activity back stack, but at the activity level. When you perform a transaction and add it to the back stack, pressing the system Back button will reverse that transaction and restore the previous fragment state inside the same activity.

To add a transaction to the back stack, call addToBackStack before commit():

supportFragmentManager.beginTransaction()
    .replace(R.id.fragment_container, DetailsFragment())
    .addToBackStack("details")
    .commit()

The string "details" is an optional name for that back stack entry. You can use it to identify or pop to that specific entry later. If you do not care about the name, you can pass null.

If you do not call addToBackStack, the transaction is not recorded. That means pressing Back will not reverse it. Instead, it will cause the activity itself to finish if there are no other fragments that manage back behavior.

A common pattern for navigation inside a single activity is to replace the current fragment and call addToBackStack. This pattern creates a fragment navigation history so the user can move backward inside the same activity.

Key behavior: A transaction on the back stack is reversed when the user presses Back. Reversing a transaction does the opposite of each operation inside it, for example remove becomes add and add becomes remove.

Popping the Back Stack

The fragment back stack can also be controlled programmatically through the FragmentManager. To remove the top entry and reverse its transaction, you call:

supportFragmentManager.popBackStack()

This has the same visual effect as the user pressing the Back button when the current navigation change was added to the fragment back stack.

If you used named entries, you can pop back to a specific point:

supportFragmentManager.popBackStack(
    "details",
    0
)

The second parameter controls how far you pop. With 0, the back stack will pop entries down to but not including the named one. With the flag FragmentManager.POP_BACK_STACK_INCLUSIVE it will also remove the named entry itself.

Programmatic popping is useful when you want to reset part of the navigation, for example to return to a root fragment after the user completes a flow.

Managing Multiple Transactions and Ordering

You can perform several operations in one transaction and they will execute as a single atomic change. This is useful when you need the UI to change all at once, without intermediate states visible to the user.

A transaction that adds and removes fragments together might look like this:

supportFragmentManager.beginTransaction()
    .remove(oldFragment)
    .add(R.id.fragment_container, newFragment)
    .addToBackStack(null)
    .commit()

The order in which you call add, replace, and remove within the same transaction does matter for how the transaction will be reversed when it is popped from the back stack. In general, you should keep the order logical from the user perspective, and let the system reverse that order when the back stack is popped.

You can also set custom animations or transitions on a transaction so that fragment changes are animated. This belongs more closely to animation topics, but the basic connection is that animations for fragment transitions are configured directly on the FragmentTransaction before you commit it.

Finding Fragments After a Transaction

After you add or replace a fragment, you often need to interact with it. You can store a direct reference when you create it, or you can look it up through the FragmentManager using the container ID or the tag that you passed in the transaction.

To find a fragment by its container view ID:

val fragment = supportFragmentManager
    .findFragmentById(R.id.fragment_container)

To find a fragment by tag:

val fragment = supportFragmentManager
    .findFragmentByTag("DETAILS_TAG")

Using tags is especially useful when you have multiple fragments in the same container over time and you want to retrieve a specific one that you know by name.

Remember that the fragment must actually be added and the transaction must be committed before findFragmentById or findFragmentByTag will return a non null result. If you call these too early, for example before commit() has completed, you can get null.

State Loss and Lifecycle Timing

Fragment transactions interact closely with the activity lifecycle. If a transaction is committed after the system has saved the activity state, there is a risk of state loss. This situation happens when the user leaves the activity and the system prepares it for possible destruction, but you still try to commit a new fragment change.

In that case, if you use commit(), the framework can throw an exception to warn you about the problem. commitAllowingStateLoss() suppresses that exception, but you accept that the UI state might not match what is restored if the process is killed and recreated.

To avoid this, try to perform fragment transactions during appropriate lifecycle phases, for example in response to user actions while the activity is resumed, and not during or after onSaveInstanceState.

Important rule: Avoid starting new fragment transactions after onSaveInstanceState. Use lifecycle aware patterns and user driven actions to keep transactions aligned with a visible, active activity.

Understanding fragment transactions is essential for reliable navigation and dynamic UIs in Android apps. By mastering FragmentManager, basic operations, back stack handling, and commit timing, you gain precise control over how your fragments appear, disappear, and respond to user navigation.

Views: 1

Comments

Please login to add a comment.

Don't have an account? Register now!