Skip to content

fix: keep TabView body state across rebuilds that construct fresh Tab widgets - #1352

Open
smileheart0708 wants to merge 1 commit into
bdlukaa:masterfrom
smileheart0708:fix/tabview-fresh-instance-rebuild
Open

fix: keep TabView body state across rebuilds that construct fresh Tab widgets#1352
smileheart0708 wants to merge 1 commit into
bdlukaa:masterfrom
smileheart0708:fix/tabview-fresh-instance-rebuild

Conversation

@smileheart0708

Copy link
Copy Markdown

Fixes #1349. Follow-up to #1344, which fixed the reorder desync but introduced a regression for the most common way of building tabs.

What this PR changes

#1344 keyed the body PageView pages by tab identity (ValueKey<Tab>(item)). Tab does not implement ==, so apps that construct their tabs in build() — the standard Flutter pattern, e.g. one Tab per item derived from app state — get brand-new page keys on every rebuild. The lazy viewport then treats every old page as removed and tears down the visible body, losing scroll positions, text field state and focus on each parent rebuild (described in #1349).

This PR keys each body page (and each strip entry, which had the same identity churn since before #1344) on the caller-provided Tab.key when available, falling back to tab identity for keyless tabs:

  • Key stability: ValueKey<Object?>(tab.key ?? tab) compares caller keys by value, so rebuilds that construct fresh Tab widgets with stable keys no longer tear the body down.
  • Reorder fix preserved: findChildIndexCallback resolves old page keys to their new index with the same rule, so a body (and its state) still relocates to its tab's new index on reorder — for keyed and keyless (cached-instance) tabs alike.
  • For tabs without a key, behavior is unchanged from 4.16.1.

No public API changes; CHANGELOG.md updated under ## [next].

Tests

Two regression tests added, both verified to fail on current master and pass with this change:

  1. TabView keeps body state across rebuilds that construct fresh Tab widgets — a stateful body's counter survives a rebuild that constructs brand-new Tab widgets carrying the same keys.
  2. TabView keeps body glued to its tab across a reorder of fresh Tab widgets — the counter still follows its tab when the tabs are reordered and every rebuild constructs fresh Tab widgets.

The existing reorder regression test from #1344 (cached Tab instances, no caller keys) still passes unchanged. Full suite: flutter test — all tests pass. dart format clean; no new analyzer findings in touched files.

Pre-launch Checklist

  • I have updated CHANGELOG.md with my changes
  • I have run "dart format ." on the project
  • I have added/updated relevant documentation

… widgets

bdlukaa#1344 keyed the body PageView pages by tab identity (ValueKey<Tab>). Tab
does not implement ==, so apps that construct their tabs in build() — the
standard Flutter pattern — get brand-new page keys on every rebuild: the
lazy viewport treats every old page as removed and tears down the visible
body, losing scroll positions, text field state and focus on each parent
rebuild.

Prefer the caller-provided Tab.key as the page identity (compared by
value), falling back to tab identity for keyless tabs. Apply the same
keying to the tab strip so its elements survive rebuilds too. Reorders
still relocate each body (and its state) to its tab's new index via
findChildIndexCallback.

Adds regression tests: a stateful body's counter survives rebuilds that
construct fresh Tab widgets with stable keys, and still follows its tab
across a reorder.
@bdlukaa
bdlukaa requested a lite review from Copilot August 30, 2026 04:55

This comment was marked as off-topic.

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.

🐛 TabView 4.16.1 regression: every parent rebuild disposes and recreates all tab bodies (ValueKey<Tab> uses identity equality)

2 participants