Skip to content

Reject zeroed iOS keyboard frames in KeyboardCompatibleView - #3803

Closed
amit-hinge wants to merge 1 commit into
GetStream:developfrom
amit-hinge:fix/reject-zero-frame-keyboard-events
Closed

amit-hinge wants to merge 1 commit into
GetStream:developfrom
amit-hinge:fix/reject-zero-frame-keyboard-events

Conversation

@amit-hinge

Copy link
Copy Markdown

🎯 Goal

Companion to the repro in #3802 β€” this is the fix we applied on our side, opened as a draft in case it's useful to you. Not asking for a merge; happy for you to take it, adapt it, or close it.

_relativeKeyboardHeight guards against a zeroed keyboard frame only when AccessibilityInfo.prefersCrossFadeTransitions() is true:

if (
  Platform.OS === 'ios' &&
  keyboardFrame.screenY === 0 &&
  (await AccessibilityInfo.prefersCrossFadeTransitions())
) {
  return 0;
}

But iOS reports a zeroed frame in at least one other situation: locking the device while a text input is focused emits a burst of keyboardWillShow/keyboardDidShow events with all endCoordinates zeroed (screenY: 0, height: 0, duration: 0). Prefer Cross-Fade Transitions is a sub-setting of Reduce Motion, so it's off for the vast majority of users and the guard effectively never fires for them.

When a zeroed frame gets through, keyboardY becomes -keyboardVerticalOffset and relativeHeight resolves to frame.y + frame.height β€” the view's own bottom edge β€” which is then applied as paddingBottom. Because the grown frame is re-measured by _onLayout and feeds the next bogus event, it compounds.

Worth noting for context: KeyboardCompatibleView itself is largely shielded today, because _handleAppStateChange calls unsetKeyboardListeners() on background, so it isn't subscribed during the lock window. In our app the same flawed guard bit us via RN's own KeyboardAvoidingView (which has no AppState handling) nested inside yours β€” that's what #3802 reproduces. So this change is defence-in-depth rather than a fix for a live bug in the SDK, which is why it's a draft.

πŸ›  Implementation details

Make the guard unconditional β€” a screenY of 0 is never a real keyboard position:

if (Platform.OS === 'ios' && keyboardFrame.screenY === 0) {
  return 0;
}

Dropping that accessibility call removes the only await in the chain, so _relativeKeyboardHeight, _updateBottomIfNecessary and _onLayout become synchronous. That's required by the require-await lint rule, and it also removes three microtask hops from the keyboard path. Happy to split that into its own commit if you'd prefer the guard change in isolation.

🎨 UI Changes

None on the SDK's own surfaces. The only behavioral difference is on the zeroed-frame path, which previously produced a bogus padding value.

πŸ§ͺ Testing

  • No existing tests cover KeyboardCompatibleView, and nothing else in package/src references prefersCrossFadeTransitions, so there were no mocks to update.
  • Verified the equivalent one-line change against React Native's KeyboardAvoidingView on a physical iOS device (via patch-package in our app): the guard fires on the zeroed frames, the composer no longer inflates, and the flicker is gone.
  • We have not separately exercised the SDK-side change on a device, since unsetKeyboardListeners() means this path isn't reachable during the lock window for KeyboardCompatibleView itself.

Also worth flagging: this same gated guard is still present in React Native main, so the underlying defect is upstream too.

β˜‘οΈ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

Draft β€” shared for your consideration alongside the repro in #3802.

…itionally

The guard for keyboardFrame.screenY === 0 was gated behind
AccessibilityInfo.prefersCrossFadeTransitions(), but iOS reports a zeroed frame
in other cases too β€” notably when the device is locked while a text input is
focused, which emits a burst of keyboardWillShow/keyboardDidShow events with all
coordinates zeroed. Prefer Cross-Fade Transitions is a sub-setting of Reduce
Motion and off for most users, so the guard rarely fired.

When a zeroed frame reaches the height math, keyboardY becomes
-keyboardVerticalOffset and relativeHeight resolves to the view's own bottom
edge, which is then applied as paddingBottom. A screenY of 0 is never a real
keyboard position, so reject it regardless of the accessibility setting.

Dropping that accessibility call removes the only await in the chain, so
_relativeKeyboardHeight, _updateBottomIfNecessary and _onLayout are now
synchronous β€” required by the require-await lint rule, and it removes three
microtask hops from the keyboard path.
@amit-hinge amit-hinge closed this Sep 15, 2026
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