The many flavors of commit()

For when you are committed to choosing the right commit() for your FragmentTransaction

FragmentTransaction in the support library now provides four different ways to commit a transaction:

You have probably also encountered one of these alongside a call to executePendingTransactions().

What do each of these do, and which one should you be using? Let’s explore each one in more depth and find out!


commit() vs commitAllowingStateLoss()

At some point most Android developers who have used Fragments have also run into an IllegalStateException saying you can’t perform a commit after onSaveInstanceState(). Alex Lockwood has a great blog post that explores why this exception is thrown, but many developers want to know is what it means for their applications.

Arggggg!

commit() and commitAllowingStateLoss() are almost identical in their implementation. The only difference is that when you call commit(), the FragmentManager checks if it has already saved its state. If it has already saved its state, it will throw an IllegalStateException.

So what state do you lose if you call commitAllowingStateLoss() after onSaveInstanceState()? The answer is that you might lose the FragmentManager’s state and by extension the state of any Fragments added or removed since onSaveInstanceState().

Here’s a practical example:

  1. Your Activity is displayed and is currently showing FragmentA
  2. Your Activity is sent to the background (onStop() and onSaveInstanceState() are called)
  3. In response to some sort of event, you replace FragmentA with FragmentB and call commitAllowingStateLoss().

At this point, one of two things can happen when the user comes back to your application:

  • If the system killed off your application to make room for another application, then your application will be recreated with the saved state made in step 2. FragmentB will not be visible.
  • If the system did not need to kill your application (and thus the application is still in memory), then it will be brought back to the foreground and FragmentB will still be displayed.
    The next time the Activity stops, the state including FragmentB will be saved.
A simplified flow illustrating when you might lose state

Here is a GitHub project that demonstrates this. If you turn on the “Don’t Keep Activities” developer option in your device’s settings, you will experience the first scenario in which the state is truly lost. If you have that setting off, you will experience the second scenario in which no state is lost.

Which one should you choose? That depends on what you are committing and whether you would be okay with losing that commit.

commit(), commitNow(), and executePendingTransactions()

The other variants of commit() specify when the transaction occurs. The documentation for commit() offers this explanation for timing:

Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.

What this means in practice is that you can perform any number of transactions at a time, and none of them will actually happen until the next time the main thread is ready. This includes adding, removing, and replacing Fragments in addition to popping the back stack via popBackStack().

Sometimes you want your transactions to happen immediately. Previously developers accomplished this by calling executePendingTransactions() after calling commit(). executePendingTransactions() will take all those transactions that you currently have queued up and will process them immediately.

Version 24.0.0 of the support library added commitNow() as a better alternative to executePendingTransactions(). The former only executes the current transaction synchronously, whereas the latter will execute all of the transactions you have committed and are currently pending. commitNow() prevents you from accidentally executing more transactions than you actually want to execute.

The caveat is that you can’t use commitNow() with a transaction that you are adding to the back stack. Think about it this way- if you were to add a transaction to the back stack via commit() then immediately add a transaction to the back stack via commitNow(), what does the back stack look like? Because the framework can’t provide any guarantees regarding the ordering here, it simply isn’t supported.

Should Transaction 1 be at the top of the backstack because it was committed first, or at the bottom because it executed last? ¯\_(ツ)_/¯

On a side note, popBackStack() has a popBackStackImmediate() counterpart, which performs similarly to commit() and commitNow(). The former is asynchronous, the latter is synchronous.

Which one should you use?

  • If you need synchronicity and you are not adding your transaction to the back stack, use commitNow(). The support library uses this in FragmentPagerAdapter to guarantee that the correct pages have been added or removed at the end of an update. In general, it is fine to use this any time you are performing a transaction that you are not adding to the back stack.
  • If you are performing multiple transactions, don’t need synchronicity, or are adding transactions to the back stack, you should stick with commit().
  • Use executePendingTransactions() if you need to ensure that a set of transactions happen by a given point in time.
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.