Skip to content

Replace IndexedStack visibility children with _VisibilityScope + ExcludeFocus#184884

Merged
auto-submit[bot] merged 10 commits into
flutter:masterfrom
victorsanni:indexed-stack-positioned
Apr 21, 2026
Merged

Replace IndexedStack visibility children with _VisibilityScope + ExcludeFocus#184884
auto-submit[bot] merged 10 commits into
flutter:masterfrom
victorsanni:indexed-stack-positioned

Conversation

@victorsanni

@victorsanni victorsanni commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

IndexedStack wraps every child with Visibility.maintain, which has this widget tree:

Visibility → _VisibilityScope → _Visibility → IgnorePointer → ExcludeFocus → child

_Visibility and IgnorePointer are RenderObjectWidgets, so they insert RenderObjects between RenderIndexedStack and the child, breaking the ParentDataWidget chain — so if a Positioned is a direct child of IndexedStack, its StackParentData never reaches the RenderIndexedStack and the widget crashes.

This PR wraps the child instead with only _VisibilityScope and ExcludeFocus, which do not insert RenderObjects. This works because the _Visibility/IgnorePointer wrappers were redundant anyway as RenderIndexedStack already skips painting, hit-testing, and semantics for non-selected children.

However, _VisibilityScope is private to visibility.dart, and so we either have to:

  1. Expose it as a new public API,
  2. Mark it as @internal and don't export it from widgets.dart, or
  3. Move IndexedStack's implementation to visibility.dart

I chose the 2nd option since it preserves existing behavior without impairing API findability.

EDIT: @Renzo-Olivares pointed out that the style guide warns against semi-private APIs:

Flutter packages should not have "private" APIs other than those that are prefixed with underscores.

When developing new features in Flutter packages, one should follow the philosophy: Only expose the APIs that are necessities to the features.

Since the private classes in dart language are file-bound, this may often result in large file sizes. In Flutter, this is considered to be more preferable than creating multiple smaller files but exposing intermediate classes that are not needed to use the features.

so option 3 seems like the best solution. API findability might be impaired, but there are ways to mitigate this (.e.g rename visibility.dart to indexed_stack.dart since IndexedStack is a downstream consumer of visibility.dart)

Fixes IndexedStack no longer supports Positioned

@github-actions github-actions Bot added the framework flutter/packages/flutter repository. See also f: labels. label Apr 10, 2026
@victorsanni victorsanni changed the title Replace IndexedStack visibility children with _VisibilityScope + Excl… Replace IndexedStack visibility children with _VisibilityScope + ExcludeFocus Apr 10, 2026
@victorsanni victorsanni added the CICD Run CI/CD label Apr 10, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates IndexedStack to wrap its children in VisibilityScope and ExcludeFocus instead of the Visibility widget. This change ensures that no additional RenderObjects are introduced between the IndexedStack and its children, which allows ParentDataWidgets like Positioned to correctly apply their StackParentData. Additionally, _VisibilityScope is renamed to VisibilityScope and marked as @internal. A regression test is included to verify support for Positioned children. Feedback was provided regarding a technical inaccuracy in a documentation comment that incorrectly identifies ExcludeFocus as an inherited widget.

Comment thread packages/flutter/lib/src/widgets/basic.dart Outdated
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 10, 2026
@victorsanni victorsanni added the CICD Run CI/CD label Apr 10, 2026
navaronbracke
navaronbracke previously approved these changes Apr 10, 2026

@navaronbracke navaronbracke left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM with nit about the doc comment

Comment thread packages/flutter/lib/src/widgets/basic.dart Outdated
Comment thread packages/flutter/lib/src/widgets/visibility.dart Outdated
@victorsanni victorsanni added the CICD Run CI/CD label Apr 10, 2026
navaronbracke
navaronbracke previously approved these changes Apr 10, 2026

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is an old bug, thanks for tackling it. Reading through the description, it felt to me like a code smell that we have this kind of problem at all. It seems like a footgun.

But I was unable to think of a better way of doing things. Maybe it's the nature of IndexedStack itself that makes this tricky. I guess it's IndexedStack's responsibility to make sure that it's not breaking the use of Positioned in its children, and your solution fixes it at the IndexedStack level, so I think your solution is good.

///
/// Used internally by [Visibility] and [IndexedStack] to propagate visibility
/// information to descendants.
@internal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think we've ever used this annotation before outside of feature flags (multiwindow), right? We should be sure we want to set this precedent.

I see you considered some alternatives, did you think about duplicating the code in both places and keeping them both private? Looks like it's only 11 lines of code.

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.

I'm confident in setting the precedent, I think there are cases where we want to expose an API within the library but not outside. Sort of a middle ground between public and private.

I'm skeptical about duplicating the code, while it is a small class it feels hacky to duplicate because any changes in one implementation will need to be done in the other, and so it can be easy to go out of sync.

I know we're doing a similar duplication in tests, but in that context I actually prefer it because tests are sandboxed, trivial-ish, and the less coupled they are the better.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Once we decided on the rule of if it is allowed to use it, we could put this in the Style Guide? (a section for internal API's?)

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.

Actually @justinmc you are right here. The style guide explicitly warns about semi-private APIs:

When developing new features in Flutter packages, one should follow the philosophy:

Only expose the APIs that are necessities to the features.

Since the private classes in dart language are file-bound, this may often result in large file sizes. In Flutter, this is considered to be more preferable than creating multiple smaller files but exposing intermediate classes that are not needed to use the features.

So the solution here might be to move IndexedStack into visibility.dart. What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's a little weird having Visibility and SliverVisibility inside of indexed_stack.dart, but I guess it's no weirder than the 8000+ line basic.dart itself and all of the different stuff in there. Also, I think it's wise to keep _VisibilityScope private (as you have done) because it seems too specific to be a public widget. So I'm on board for lack of a better idea.

@victorsanni victorsanni requested a review from justinmc April 13, 2026 22:29
Comment thread packages/flutter/lib/src/widgets/visibility.dart Outdated
export 'src/widgets/value_listenable_builder.dart';
export 'src/widgets/view.dart';
export 'src/widgets/viewport.dart';
export 'src/widgets/visibility.dart';

@Renzo-Olivares Renzo-Olivares Apr 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does hiding VisibilityScope here possibly contradict the style guide https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#structure wrt exporting public APIs?

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.

Hmm... It looks like it for sure.

When developing new features in Flutter packages, one should follow the philosophy:

Only expose the APIs that are necessities to the features.

Since the private classes in dart language are file-bound, this may often result in large file sizes. In Flutter, this is considered to be more preferable than creating multiple smaller files but exposing intermediate classes that are not needed to use the features.

So should we change the solution to option 3 in the PR description (move IndexedStack impl. to visibility.dart?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we have a "visibility_scope.dart" file? I agree that we should follow the written rule in the style guide, but then discoverability might take a hit (as discussed before)

/// Used internally by [Visibility] and [IndexedStack] to propagate visibility
/// information to descendants.
@internal
class VisibilityScope extends InheritedWidget {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd be curious if there are similar APIs like this that we have exposed in the framework, i.e. a simple inherited widget that provides a bool to certain components.

@github-actions github-actions Bot added f: scrolling Viewports, list views, slivers, etc. and removed CICD Run CI/CD labels Apr 14, 2026
@victorsanni victorsanni added the CICD Run CI/CD label Apr 14, 2026
expect(tapped, false);
});

testWidgets('IndexedStack supports Positioned children', (WidgetTester tester) async {

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 is the only test added in this PR, the others were just moved over from stack_test.dart.

final List<Widget> children;

@override
Widget build(BuildContext context) {

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.

The build method is changing, but the rest is just copied over from basic.dart.

Comment thread packages/flutter/test/widgets/indexed_stack_test.dart
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 15, 2026
@victorsanni victorsanni added the CICD Run CI/CD label Apr 15, 2026

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 👍

@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 16, 2026
@victorsanni victorsanni added the CICD Run CI/CD label Apr 16, 2026

@Renzo-Olivares Renzo-Olivares left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@victorsanni victorsanni added the autosubmit Merge PR when tree becomes green via auto submit App label Apr 21, 2026
@auto-submit auto-submit Bot added this pull request to the merge queue Apr 21, 2026
Merged via the queue into flutter:master with commit 3955e2b Apr 21, 2026
87 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Apr 21, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Apr 23, 2026
flutter/flutter@3d0e822...5e4f169

2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from f12c89580766 to 11640d1cbc5c (3 revisions) (flutter/flutter#185418)
2026-04-22 engine-flutter-autoroll@skia.org Roll Packages from 7c8e13e to 4a2091d (2 revisions) (flutter/flutter#185417)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from f765937d0639 to f12c89580766 (1 revision) (flutter/flutter#185410)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from 635b78342e75 to f765937d0639 (1 revision) (flutter/flutter#185406)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from cda2af3f5c2e to 635b78342e75 (3 revisions) (flutter/flutter#185393)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from 632a41e2baba to cda2af3f5c2e (3 revisions) (flutter/flutter#185390)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from 019de7776cfa to 632a41e2baba (3 revisions) (flutter/flutter#185383)
2026-04-22 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Update libimobiledevice and dependencies (#181932)" (flutter/flutter#185385)
2026-04-22 rmolivares@renzo-olivares.dev Fix SelectableRegion crash when the selection starts in a scrollable child but does not select anything initially (flutter/flutter#184420)
2026-04-21 34871572+gmackall@users.noreply.github.com Fix timeout when `hybrid_android_views` fails `MotionEvent recomposition` (flutter/flutter#185003)
2026-04-21 srawlins@google.com [flutter] Remove dead check on null being in a set of non-nullables (flutter/flutter#184100)
2026-04-21 737941+loic-sharma@users.noreply.github.com Update the text input triage process to route to platform teams (flutter/flutter#185225)
2026-04-21 scheglov@google.com Compatibility bridge for analyzer 12 and 13. (flutter/flutter#185360)
2026-04-21 magder@google.com new_gallery_macos_impeller__transition_perf out of bringup (flutter/flutter#185355)
2026-04-21 engine-flutter-autoroll@skia.org Roll Skia from 21789d5e2fee to 019de7776cfa (9 revisions) (flutter/flutter#185365)
2026-04-21 magder@google.com Update libimobiledevice and dependencies (flutter/flutter#181932)
2026-04-21 magder@google.com platform_view_macos_impeller__start_up out of bringup (flutter/flutter#185354)
2026-04-21 magder@google.com complex_layout_scroll_perf_macos_impeller__timeline_summary out of bringup (flutter/flutter#185356)
2026-04-21 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from LPa7NLiXEZP2A7IwZ... to UdpQnaP5eSaDZd3-i... (flutter/flutter#185359)
2026-04-21 engine-flutter-autoroll@skia.org Roll Packages from 01c505f to 7c8e13e (4 revisions) (flutter/flutter#185361)
2026-04-21 737941+loic-sharma@users.noreply.github.com Improve the error if the tool cannot find the locally built engine (flutter/flutter#184546)
2026-04-21 engine-flutter-autoroll@skia.org Roll Skia from a234f0ed7245 to 21789d5e2fee (1 revision) (flutter/flutter#185349)
2026-04-21 victorsanniay@gmail.com Replace IndexedStack visibility children with _VisibilityScope + ExcludeFocus (flutter/flutter#184884)
2026-04-21 dacoharkes@google.com [data_assets] Try fix #184505 (flutter/flutter#185330)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@victorsanni victorsanni deleted the indexed-stack-positioned branch May 5, 2026 18:07
creatorpiyush pushed a commit to creatorpiyush/packages that referenced this pull request Jun 10, 2026
…r#11565)

flutter/flutter@3d0e822...5e4f169

2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from f12c89580766 to 11640d1cbc5c (3 revisions) (flutter/flutter#185418)
2026-04-22 engine-flutter-autoroll@skia.org Roll Packages from 7c8e13e to 4a2091d (2 revisions) (flutter/flutter#185417)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from f765937d0639 to f12c89580766 (1 revision) (flutter/flutter#185410)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from 635b78342e75 to f765937d0639 (1 revision) (flutter/flutter#185406)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from cda2af3f5c2e to 635b78342e75 (3 revisions) (flutter/flutter#185393)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from 632a41e2baba to cda2af3f5c2e (3 revisions) (flutter/flutter#185390)
2026-04-22 engine-flutter-autoroll@skia.org Roll Skia from 019de7776cfa to 632a41e2baba (3 revisions) (flutter/flutter#185383)
2026-04-22 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Update libimobiledevice and dependencies (#181932)" (flutter/flutter#185385)
2026-04-22 rmolivares@renzo-olivares.dev Fix SelectableRegion crash when the selection starts in a scrollable child but does not select anything initially (flutter/flutter#184420)
2026-04-21 34871572+gmackall@users.noreply.github.com Fix timeout when `hybrid_android_views` fails `MotionEvent recomposition` (flutter/flutter#185003)
2026-04-21 srawlins@google.com [flutter] Remove dead check on null being in a set of non-nullables (flutter/flutter#184100)
2026-04-21 737941+loic-sharma@users.noreply.github.com Update the text input triage process to route to platform teams (flutter/flutter#185225)
2026-04-21 scheglov@google.com Compatibility bridge for analyzer 12 and 13. (flutter/flutter#185360)
2026-04-21 magder@google.com new_gallery_macos_impeller__transition_perf out of bringup (flutter/flutter#185355)
2026-04-21 engine-flutter-autoroll@skia.org Roll Skia from 21789d5e2fee to 019de7776cfa (9 revisions) (flutter/flutter#185365)
2026-04-21 magder@google.com Update libimobiledevice and dependencies (flutter/flutter#181932)
2026-04-21 magder@google.com platform_view_macos_impeller__start_up out of bringup (flutter/flutter#185354)
2026-04-21 magder@google.com complex_layout_scroll_perf_macos_impeller__timeline_summary out of bringup (flutter/flutter#185356)
2026-04-21 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from LPa7NLiXEZP2A7IwZ... to UdpQnaP5eSaDZd3-i... (flutter/flutter#185359)
2026-04-21 engine-flutter-autoroll@skia.org Roll Packages from 01c505f to 7c8e13e (4 revisions) (flutter/flutter#185361)
2026-04-21 737941+loic-sharma@users.noreply.github.com Improve the error if the tool cannot find the locally built engine (flutter/flutter#184546)
2026-04-21 engine-flutter-autoroll@skia.org Roll Skia from a234f0ed7245 to 21789d5e2fee (1 revision) (flutter/flutter#185349)
2026-04-21 victorsanniay@gmail.com Replace IndexedStack visibility children with _VisibilityScope + ExcludeFocus (flutter/flutter#184884)
2026-04-21 dacoharkes@google.com [data_assets] Try fix #184505 (flutter/flutter#185330)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IndexedStack no longer supports Positioned

4 participants