Skip to content

fix(Android): don't block window insets animation dispatch from ScreenFooter#4149

Open
stachbial wants to merge 1 commit into
software-mansion:mainfrom
stachbial:fix/screen-footer-decor-insets-animation-callback
Open

fix(Android): don't block window insets animation dispatch from ScreenFooter#4149
stachbial wants to merge 1 commit into
software-mansion:mainfrom
stachbial:fix/screen-footer-decor-insets-animation-callback

Conversation

@stachbial

Copy link
Copy Markdown

Description

On Android, ScreenFooter installs a WindowInsetsAnimationCompat.Callback with DISPATCH_MODE_STOP on the activity's decor view (in its init block) and never removes it. Android keeps a single insets-animation callback slot per view, and DISPATCH_MODE_STOP stops dispatch to the entire subtree of the view it is set on — and the decor view is the root of the window. As a result, once a single formSheet with unstable_sheetFooter mounts, no other view in the window ever receives onStart / onProgress / onEnd again, and the callback stays installed even after the footer unmounts.

With edge-to-edge enabled (the default since React Native 0.81, enforced on Android 15+), WindowInsetsAnimation dispatch is the only keyboard signal libraries like reanimated (useAnimatedKeyboard) and react-native-keyboard-controller rely on. So mounting one sheet footer permanently breaks keyboard handling/animations in the whole app until the process is restarted.

The interference was already flagged as a known issue in the code itself:

// Note that we do override insets animation on given view. I can see it interfering e.g.
// with reanimated keyboard or even other places in our code. Need to test this.

Minimal reproduction

React Native 0.83 / react-native-screens 4.x, Android only (reproduced on API 33 and API 36). Included in this PR as apps/src/tests/issue-tests/TestScreenFooterKeyboardInsets.tsx — a Home screen with a TextInput translated by useAnimatedKeyboard, plus a formSheet route with unstable_sheetFooter.

Steps (against unpatched main):

  1. Focus the TextInput — the input animates up with the keyboard (insets animation dispatch works).
  2. Dismiss the keyboard, open the form sheet (the footer mounts), close the sheet.
  3. Focus the TextInput again — the keyboard animation no longer runs (useAnimatedKeyboard reports nothing), and it never recovers for the lifetime of the process.

Changes

  • Changed the insetsAnimation callback's dispatch mode from DISPATCH_MODE_STOP to DISPATCH_MODE_CONTINUE_ON_SUBTREE. The callback never consumes insets in onProgress, so continuing dispatch is safe and other consumers in the window keep receiving insets animations.
  • Moved callback registration out of the init block and tied it to window attachment: it is registered in onAttachedToWindow and unregistered (slot restored to null) in onDetachedFromWindow. A WeakReference<ScreenFooter> in the companion object tracks which footer currently occupies the decor view's single callback slot, so one footer's detach cannot clear a callback registered by a more recently attached footer. The decor view is now resolved null-safely via reactContext.currentActivity?.window?.decorView.

Why keep the callback on the decor view at all

The footer cannot listen on itself or its Screen parent: every formSheet's Screen view carries its own DISPATCH_MODE_STOP callback (ScreenStackFragment), so nothing inside the sheet's subtree receives insets-animation dispatch. Listening on the decor view is the pragmatic way for the footer to keep tracking the keyboard — this PR keeps that approach and only removes its side effects on the rest of the window. A deeper alternative would be to drive the footer from SheetDelegate's existing keyboard-insets handling (it already observes IME progress for the sheet) and drop the decor-view callback entirely — happy to explore that instead if you prefer, but it felt too invasive for a first contribution.

Test plan

  • apps/src/tests/issue-tests/TestScreenFooterKeyboardInsets.tsx (added in this PR): after opening and closing the form sheet (footer mount + unmount), focusing the TextInput on the Home screen still animates with the keyboard. On unpatched main, step 3 of the reproduction fails.
  • apps/src/tests/issue-tests/TestFormSheet.tsx with the commented-out unstable_sheetFooter: FormSheetFooter option re-enabled: the footer still tracks the sheet and stays positioned above the keyboard when a TextInput inside the sheet is focused (the footer's own insets-animation handling is unchanged — it still receives onStart / onProgress / onEnd while attached).

Checklist

  • Included code example that can be used to test this change.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

…nFooter

ScreenFooter installed a WindowInsetsAnimationCompat.Callback with
DISPATCH_MODE_STOP on the activity decor view and never removed it. Since
DISPATCH_MODE_STOP halts dispatch to the whole subtree - and the decor view
is the root of the window - mounting a single formSheet footer permanently
disabled keyboard insets animations for every other consumer in the app
(reanimated useAnimatedKeyboard, react-native-keyboard-controller, ...).

Switch the callback to DISPATCH_MODE_CONTINUE_ON_SUBTREE (it never consumes
insets, so propagation is safe) and tie registration to the view's window
attachment so the decor view callback slot is restored on detach.
@stachbial stachbial marked this pull request as ready for review June 10, 2026 16:51
@t0maboro t0maboro self-requested a review June 11, 2026 07:15
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