fix(chat): build the WebView settings once per surface, not per rebuild - #390
Merged
Conversation
`initialSettings:` called `chatWebViewInAppSettings()` inside `build()`, so every rebuild of the chat surface allocated a fresh `InAppWebViewSettings` — and with it the `WebViewAssetLoader` and `AssetsPathHandler` it carries. Those are platform-interface objects: constructing one registers it with the plugin, and only the instance handed to the platform view at creation time is ever used, so every later allocation was registered and then dropped without being released. Measured on a Pixel 8 Pro emulator (profile build, 600-message chat): each chat open/close cycle leaked ~19 `AssetsPathHandler` / `MethodChannel` / `ListQueue` objects — linear over 30 cycles and surviving five forced GCs. Holding the settings in a `late final` field drops that to 1 per cycle. The preloader had the same pattern. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
hydall
added a commit
that referenced
this pull request
Sep 5, 2026
) `chatWebViewAssetLoader()` built a fresh `WebViewAssetLoader` — and with it an `AssetsPathHandler` — on every call. Each handler's `_init` registers a `MethodChannel` under a unique name and installs a method call handler on the binary messenger, and nothing ever disposes it. On Android the chat WebView is kept alive and reused (`chatWebViewKeepAliveForPlatform`). `FlutterWebViewFactory.create` returns the cached `FlutterWebView` for a known `keepAliveId` and only constructs a new one — the sole place `initialSettings` is parsed — when there is no cached instance. So every mount after the first handed the native side settings it never reads, while the handler they carried stayed registered on the Dart side forever. Disposing per mount would be wrong: whichever mount created the native WebView owns the live handler, and that is the preloader in practice. One shared loader sidesteps the ownership question — the native side only ever uses a single one, and `handle()` is stateless. Verified on a Pixel 8 Pro emulator (profile build, 600-message chat): `AssetsPathHandler` and `MethodChannel` now stay flat at 1 and 23 across 30 chat open/close cycles (+0, +0, +0), down from +10 per 10 cycles after #390 and +190 before it. Dart heap is flat too. The chat renders with styles, avatars and the Context card intact, and the log carries no asset-loading errors. Co-authored-by: Claude Opus 5 <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.
initialSettings:calledchatWebViewInAppSettings()from insidebuild(), so every rebuild of the chat surface allocated a freshInAppWebViewSettingstogether with theWebViewAssetLoaderandAssetsPathHandlerit carries. Those are platform-interface objects — constructing one registers it with the plugin, and only the instance handed to the platform view at creation time is ever used, so every later allocation was registered and then dropped without being released.Changes
chat_webview_surface.dart: hold the settings in alate finalfield built once per surface instead of callingchatWebViewInAppSettings()on every build.chat_webview_preload.dart: same pattern in the preloader, which rebuilt its settings on every build until the preload finished.Verification
Profile build on a Pixel 8 Pro emulator (Android 16, x86_64) with a seeded 600-message chat, driven over adb; heap sampled through the Dart VM service with
getAllocationProfile(gc: true).AssetsPathHandler+192, +195, +190 (MethodChannel,_Channel,_ChannelCallbackRecord,ListQueuetrack it one-for-one) — linear over 30 cycles and unchanged across five consecutive forced GCs, so the objects are retained rather than uncollected.flutter analyze— 9 issues, all info/warning, identical to thenightlybaseline and none in the touched files.flutter test— 3812 pass. The 4 failures (agent_ops_localization_contract_test.dart, three clipboard cases inchat_input_bar_test.dart) reproduce on unpatchednightlyand are local-only: the contract test needs theeasy_localization:generatestep CI runs, and the clipboard cases pass in CI on ubuntu.One settings object per surface mount is still never released; that disposal belongs to the plugin side and is left for a follow-up.
🤖 Generated with Claude Code