Skip to content

fix: animation stutter on edit#3537

Merged
isekovanic merged 6 commits intodevelopfrom
fix/animation-stutter-on-edit
Apr 7, 2026
Merged

fix: animation stutter on edit#3537
isekovanic merged 6 commits intodevelopfrom
fix/animation-stutter-on-edit

Conversation

@isekovanic
Copy link
Copy Markdown
Contributor

@isekovanic isekovanic commented Apr 7, 2026

🎯 Goal

This PR fixes an Android timing bug in the message edit flow when edit is triggered from the message overlay that caused the keyboard to be immediately closed afterwards. It also fixes severe visual stuttering when changing the state to editing within the MessageComposer on both platforms.

The bug was caused by the edit action focusing the message input before PortalWhileClosingView had fully returned the teleported composer back into its normal tree. On Android, that caused the TextInput to lose focus mid keyboard animation, which immediately closed the keyboard again.

The stuttering part is caused by the fact that the keyboard opening and the state changing to editing essentially animate the layout of the same component, but through 2 different state values - often offset by a single frame. This means that it is very realistic for the MessageComposer to animate its layout height, bottom padding and other layout props all offset from each other by a frame, making the animations feel wonky.

The fix separates the problem into two explicit stages:

  1. wait for portal close handoff to settle
  2. wait for the keyboard to be open before running the edit callback

This preserves the original edit behavior while making the focus timing safe.

When long pressing a message and tapping Edit, the expected behaviour is:

  1. close the overlay
  2. focus the composer input
  3. open the keyboard
  4. enter edit mode

On Android, that flow was unstable when the overlay was still closing through PortalWhileClosingView.

What was actually happening:

  1. the overlay started closing
  2. the composer was still teleported into the overlay host
  3. the edit action focused the input too early
  4. react-native-teleport moved the composer back into the normal tree after focus had already been applied
  5. the input lost focus during that handoff
  6. the keyboard started opening and then immediately closed again

This showed up much more clearly in release builds than in debug builds, because of the fact that no animation frames are actually skipped. In other words, the invocation of the scheduled actions to execute after the context menu closes happens way too fast in the React lifecycle.

So, the fix for both of these things looks something like this:

  • Immediate focus after overlay close and only invoke edit after the keyboard has been opened (if applicable)
  • Waiting for two requestAnimationFrames worked reliably:
    • one frame for the portal retarget / React commit
    • one additional frame for the native view hierarchy and TextInput host to settle

We also considered solving this directly in the overlay queue, but the committed fix keeps the behavior local to the edit action instead.

In the future, if we figure that 2 RAFs are not enough (on 120mhz phones for example), there is an alternative solution that can introduce a relatively complex registry on the PortalWhileClosingView layer that waits for true layout settle before doing any teleport sensitive actions (like focusing an input). I've yet to reproduce a case where they aren't enough, though - so let's keep it simple for now.

🛠 Implementation details

🎨 UI Changes

iOS
Before After
Android
Before After

🧪 Testing

☑️ 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

@isekovanic isekovanic requested review from oliverlaz and szuperaz April 7, 2026 07:23
expect(screen.getByTestId('channel-list')).toBeTruthy();
await expectAllChannelsWithStateToBeInDB(screen.queryAllByLabelText);
});
}, { timeout: 5000 });
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test started timing out locally, maybe due to the hook barrel imports becoming larger ? It was likely on the edge before. Anyway, the offline support tests are really due a refactor anyway so for now we just extend the timeout.

closePollCreationDialog,
compressImageQuality,
CreatePollContent,
// TODO: probably not needed anymore, please check
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely not used by the underlying unmemoized component but let's do the cleanup in a separate PR to make sure some parts of the state do not weirdly depend on this rerender.

@Stream-SDK-Bot
Copy link
Copy Markdown
Contributor

Stream-SDK-Bot commented Apr 7, 2026

SDK Size

title develop branch diff status
js_bundle_size 348 KB 351 KB +3069 B 🔴

@isekovanic isekovanic merged commit 369c785 into develop Apr 7, 2026
4 of 5 checks passed
@isekovanic isekovanic deleted the fix/animation-stutter-on-edit branch April 7, 2026 07:40
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.

3 participants