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:

Your Activity is displayed and is currently showing FragmentA Your Activity is sent to the background (onStop() and onSaveInstanceState() are called) 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?