fix(chat): share one WebView asset loader instead of one per mount - #391
Merged
Conversation
`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.
Follow-up to #390, which cut the per-rebuild allocation but left one leaked
AssetsPathHandlerper chat surface mount.chatWebViewAssetLoader()built a freshWebViewAssetLoaderon every call. EachAssetsPathHandlerregisters aMethodChannelunder a unique name in its_initand installs a method call handler on the binary messenger; nothing disposes it, and the binding's handler map holds it for the process lifetime.On Android the chat WebView is kept alive and reused.
FlutterWebViewFactory.createreturns the cachedFlutterWebViewfor a knownkeepAliveIdand only constructs a new one — the only placeinitialSettingsis parsed — when there is no cached instance. Every mount after the first therefore handed the native side settings it never reads, while the handler they carried stayed registered on the Dart side.Disposing per mount would be the wrong shape: whichever mount created the native WebView owns the live handler, and in practice that is the preloader, not the surface. Sharing one loader sidesteps the ownership question entirely — the native side only ever uses one, and
handle()is stateless.Changes
chat_webview_settings.dart:chatWebViewAssetLoader()returns a lazily created sharedWebViewAssetLoaderinstead of a new one per call.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).AssetsPathHandleracross 30 chat open/close cycles:1 → 1 → 1 → 1(+0, +0, +0).MethodChannelflat at 23. For comparison: +190 per 10 cycles before fix(chat): build the WebView settings once per surface, not per rebuild #390, +10 after it.appassets.androidplatform.netas before.flutter analyze— 9 issues, all info/warning, identical to thenightlybaseline, 0 errors.flutter test— 3812 pass, includingchat_webview_keep_alive_test.dartandchat_webview_security_test.dart, which cover these settings helpers. The same 4 failures as on unpatchednightlyremain and are local-only (the contract test needs CI'seasy_localization:generatestep; the clipboard cases pass in CI on ubuntu).🤖 Generated with Claude Code