Skip to content

Fix NullPointerException in ActivityScenario.close() for finishing activities in a transient lifecycle stage. - #2518

Open
copybara-service[bot] wants to merge 1 commit into
mainfrom
piper_1003085587
Open

copybara-service[bot] wants to merge 1 commit into
mainfrom
piper_1003085587

Conversation

@copybara-service

@copybara-service copybara-service Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Fix NullPointerException in ActivityScenario.close() for finishing activities in a transient lifecycle stage.

ActivityScenario.moveToState() (and therefore close()) assumed that once the
main thread is idle the activity is in a steady lifecycle state. That is not
always true: lifecycle transitions are driven by the system, and the main
thread can be idle while a transition is still pending. For example, an
activity that finishes itself while stopped can receive onRestart() but not
onStart(), and then stay in the RESTARTED stage until the system destroys it.
A common way to hit this is launchActivityForResult() + getResult() followed by
close(), since getResult() returns as soon as the result is delivered, before
the activity is destroyed.

In that window moveToState() failed with
"NullPointerException: Current state was null unexpectedly. Last stage = RESTARTED".

When the activity is in a transient stage but is finishing (so it is
guaranteed to be destroyed), moveToState() now waits for it to settle instead
of failing. The wait is skipped on the main thread (e.g. Robolectric), where the
transition cannot progress, and for activities that are not finishing, which
keeps the previous behavior for those cases.

@copybara-service copybara-service Bot changed the title Fix NullPointerException in ActivityScenario.close() for activities in a transient lifecycle stage. Fix NullPointerException in ActivityScenario.close() for finishing activities in a transient lifecycle stage. Oct 21, 2026
…tivities in a transient lifecycle stage.

ActivityScenario.moveToState() (and therefore close()) assumed that once the
main thread is idle the activity is in a steady lifecycle state. That is not
always true: lifecycle transitions are driven by the system, and the main
thread can be idle while a transition is still pending. For example, an
activity that finishes itself while stopped can receive onRestart() but not
onStart(), and then stay in the RESTARTED stage until the system destroys it.
A common way to hit this is launchActivityForResult() + getResult() followed by
close(), since getResult() returns as soon as the result is delivered, before
the activity is destroyed.

In that window moveToState() failed with
"NullPointerException: Current state was null unexpectedly. Last stage = RESTARTED".

When the activity is in a transient stage but is finishing (so it is
guaranteed to be destroyed), moveToState() now waits for it to settle instead
of failing. The wait is skipped on the main thread (e.g. Robolectric), where the
transition cannot progress, and for activities that are not finishing, which
keeps the previous behavior for those cases.

PiperOrigin-RevId: 1003085587

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant