Skip to content

fix(splash-screen): defer removeOnPreDrawListener to main looper#2548

Open
FelippoStedile wants to merge 1 commit into
ionic-team:mainfrom
FelippoStedile:patch-1
Open

fix(splash-screen): defer removeOnPreDrawListener to main looper#2548
FelippoStedile wants to merge 1 commit into
ionic-team:mainfrom
FelippoStedile:patch-1

Conversation

@FelippoStedile

Copy link
Copy Markdown

fix(splash-screen): defer removeOnPreDrawListener to main looper to avoid stuck splash / crash on Android 12+

Description

Defer the removeOnPreDrawListener(...) call by posting it to the main looper, so removal
runs on a clean pass of the UI thread after the current draw/dispatch cycle has completed.
The view and listener references are captured into locals because onPreDrawListener is
cleared immediately afterward.

Applied to both Android-12 hide paths: hide() and hideDialog().

Change Type

  • Fix
  • Feature
  • Refactor
  • Breaking Change
  • Documentation
  • Other (CI, chores, etc.)

Rationale / Problems Fixed

On Android 12+ (the OnPreDrawListener-based splash flow), hide() and hideDialog()
call removeOnPreDrawListener(...) synchronously. When hide is triggered from within the
pre-draw dispatch cycle (the common case — the app calls hide() as soon as content is
ready), the ViewTreeObserver's listener list is being mutated while it is still iterating it.

This causes one of two failure modes depending on device/OS version:

  • The listener is not actually removed, so it keeps returning false and the splash screen
    stays on screen permanently — the app appears hung at launch.
  • An IllegalStateException / concurrent-modification while dispatching pre-draw listeners.

Tests or Reproductions

Reproduced a permanently-stuck splash on launch on Android 12/13 when hide() is called
during the pre-draw phase; with this change the splash dismisses reliably and no
concurrent-modification exception is thrown. No behavior change on the dialog/legacy path.

Platforms Affected

  • Android (Android 12 / API 31+ splash screen path)
  • iOS
  • Web

Notes / Comments

There are three call sites for removeOnPreDrawListener in this file. This PR only changes
the two hide paths (hide() and hideDialog()), which currently call it synchronously and
are the ones that can run during the pre-draw dispatch cycle.

The third call site, in showWithAndroid12API(), is intentionally left unchanged: its removal
already runs inside a Handler.postDelayed(...) lambda, so it is not exposed to the same
concurrent-modification / stuck-splash issue. Wrapping it again would be redundant.

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