Replace IndexedStack visibility children with _VisibilityScope + ExcludeFocus#184884
Conversation
There was a problem hiding this comment.
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.
navaronbracke
left a comment
There was a problem hiding this comment.
LGTM with nit about the doc comment
justinmc
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
| export 'src/widgets/value_listenable_builder.dart'; | ||
| export 'src/widgets/view.dart'; | ||
| export 'src/widgets/viewport.dart'; | ||
| export 'src/widgets/visibility.dart'; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
| expect(tapped, false); | ||
| }); | ||
|
|
||
| testWidgets('IndexedStack supports Positioned children', (WidgetTester tester) async { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
The build method is changing, but the rest is just copied over from basic.dart.
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
…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
IndexedStackwraps every child withVisibility.maintain, which has this widget tree:Visibility → _VisibilityScope → _Visibility → IgnorePointer → ExcludeFocus → child_VisibilityandIgnorePointerareRenderObjectWidgets, so they insertRenderObjects betweenRenderIndexedStackand the child, breaking theParentDataWidgetchain — so if aPositionedis a direct child ofIndexedStack, itsStackParentDatanever reaches theRenderIndexedStackand the widget crashes.This PR wraps the child instead with only
_VisibilityScopeandExcludeFocus, which do not insertRenderObjects. This works because the_Visibility/IgnorePointerwrappers were redundant anyway asRenderIndexedStackalready skips painting, hit-testing, and semantics for non-selected children.However,
_VisibilityScopeis private tovisibility.dart, and so we either have to:@internaland don't export it fromwidgets.dart, orIndexedStack's implementation tovisibility.dartI 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:
so option 3 seems like the best solution. API findability might be impaired, but there are ways to mitigate this (.e.g rename
visibility.darttoindexed_stack.dartsinceIndexedStackis a downstream consumer ofvisibility.dart)Fixes IndexedStack no longer supports Positioned