fix: auto-open the keyboard when launching into a chat, without fighting Back - #3205
Open
gsxdsm wants to merge 2 commits into
Open
fix: auto-open the keyboard when launching into a chat, without fighting Back#3205gsxdsm wants to merge 2 commits into
gsxdsm wants to merge 2 commits into
Conversation
…ing Back When autoOpenKeyboard is on, opening a chat directly (from a URI, a launcher shortcut, or re-selecting an already-open chat) never raised the keyboard, because a plain requestFocus() is not enough on Android — the engine's input-connection restart during startup swallows the show request. Add a composer-owned helper (ensureComposerKeyboard) that focuses the message field and briefly retries TextInput.show until the keyboard is up. It is invoked from the composer's initState, from the resume path, and from IntentsService when re-opening an already-open chat (which never rebuilds the composer). The retry loop is deliberately short (~1s). In split-screen / multi-window the IME does not resize the app window, so neither viewInsets nor KeyboardVisibilityController ever report the keyboard visible; a long loop would keep firing TextInput.show and re-raise the keyboard every time the user pressed Back, making dismissal appear broken. Bounding it to cold-start churn and exiting early on the visibility signal keeps Back working. As a companion, the controller releases composer focus on a genuine open->closed keyboard transition so the engine does not re-raise the keyboard for the still-focused field (flutter#52599), and the keyboard-visibility subscription is now cancelled on close. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er lifecycle
Follow-ups that make the keyboard auto-open reliable and stop the render crashes it
surfaced under rapid navigation:
- Give cold (just-launched) opens a longer keyboard-retry window (~5s vs ~1s), since a
shortcut opening straight into a chat outlasts the engine's input-connection churn.
Warm opens keep the short window so split-screen Back-to-dismiss still sticks.
- Clear stale showingOverlays/showingSubRoute before raising the keyboard, so a flag left
set by a picker/details route can't make the retry loop bail ('works a few times then
stops').
- FocusNodes/text controllers are owned and disposed by ConversationViewController, not the
composer widget, so a reused controller never hands a fresh composer a disposed FocusNode.
- Dispose ConversationViewControllers when their view is permanently gone (close() only ran
on an explicit Back), and never resolve a controller whose onClose() already ran. Fixes
the leaked controllers behind the 'used after disposed' render crashes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
When Auto-open keyboard is enabled, opening a conversation directly — from a notification/URI, a launcher shortcut, or by re-selecting an already-open chat — did not raise the keyboard. A plain
requestFocus()isn't enough on Android: during startup the engine restarts its input connection and silently drops theTextInput.showrequest.This adds a composer-owned helper,
ensureComposerKeyboard, that focuses the message field and retriesTextInput.showuntil the keyboard is actually up. It's invoked from the composer'sinitState, the app-resume path (StartupTasks), andIntentsServicewhen re-opening an already-open chat.Retry window: cold vs warm
viewInsetsnorKeyboardVisibilityControllerever report the keyboard visible; a long loop there would keep firingTextInput.showand re-raise the keyboard on every Back, making dismissal look broken.Don't re-raise after dismiss
ConversationViewControllerreleases composer focus on a genuine open→closed keyboard transition (scoped to the foreground chat), so the engine doesn't re-raise the keyboard for the still-focused field (flutter/flutter#52599). Android's IME swallows the dismiss Back before anyPopScopehandler runs, so the keyboard-visibility transition is the only place to catch it.Stale overlay/sub-route guard
The retry loop bails while an overlay or sub-route is showing. Those flags live on the reused controller and are toggled by pickers / the details route / the share sheet; a skipped cleanup left one stuck
trueand blocked the keyboard on every later open ("works a few times then stops"). They're now cleared at the single entry point for every explicit keyboard-raise.Controller lifecycle / render-crash fixes
Rapid navigation surfaced "FocusNode used after disposed" / "deactivated widget" render crashes, rooted in leaked
ConversationViewControllers:onClose), not by the composer widget — so a reused controller never hands a freshly-mounted composer a disposedFocusNode.close()ran only on an explicit Back), andcvc()never returns a controller whoseonClose()already ran.Files
conversation_text_field.dart—ensureComposerKeyboard/ bounded, cold-aware retry loop; stale-flag reset; listeners added/removed (not disposing controller-owned nodes).conversation_view_controller.dart— exposes the hook; unfocus-on-dismiss; owns FocusNode/controller disposal;disposedguard; cancels the keyboard-visibility subscription.conversation_view.dart— dispose the controller when its view is gone (mobile).startup_tasks.dart— drive the keyboard through the composer helper on resume.intents_service.dart— raise the keyboard when re-opening an already-open chat.Testing
Verified on a Samsung Fold (One UI), in split-screen and fullscreen, on a release/profile build: shortcut launches raise the keyboard reliably, Back dismisses without it popping back, and rapid back-and-forth no longer throws render errors.
Supersedes #3201.