Skip to content

Commit 0e24e2e

Browse files
committed
Stop passing State objects between widgets in LayoutExplorer
Replace passing FlexLayoutExplorerWidgetState to VisualizeFlexChildren and FlexChildVisualizer with FlexLayoutExplorerScope, an InheritedWidget providing rootProperties, animation controllers, and mutation/selection callbacks. Fixes #2701
1 parent fd64bd7 commit 0e24e2e

1 file changed

Lines changed: 95 additions & 37 deletions

File tree

  • packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex

packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/flex.dart

Lines changed: 95 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ class FlexLayoutExplorerWidgetState
300300
),
301301
),
302302
child: VisualizeFlexChildren(
303-
state: this,
304303
properties: propertiesLocal,
305304
children: children,
306305
highlighted: highlighted,
@@ -383,14 +382,22 @@ class FlexLayoutExplorerWidgetState
383382
),
384383
);
385384

386-
return Container(
387-
constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight),
388-
child: Stack(
389-
children: [
390-
flexDescription,
391-
verticalAxisDescription,
392-
horizontalAxisDescription,
393-
],
385+
return FlexLayoutExplorerScope(
386+
rootProperties: propertiesLocal,
387+
onTap: onTap,
388+
onDoubleTap: onDoubleTap,
389+
markAsDirty: markAsDirty,
390+
entranceController: entranceController,
391+
entranceCurve: entranceCurve,
392+
child: Container(
393+
constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight),
394+
child: Stack(
395+
children: [
396+
flexDescription,
397+
verticalAxisDescription,
398+
horizontalAxisDescription,
399+
],
400+
),
394401
),
395402
);
396403
}
@@ -405,7 +412,6 @@ class FlexLayoutExplorerWidgetState
405412
class VisualizeFlexChildren extends StatefulWidget {
406413
const VisualizeFlexChildren({
407414
super.key,
408-
required this.state,
409415
required this.properties,
410416
required this.children,
411417
required this.highlighted,
@@ -418,7 +424,6 @@ class VisualizeFlexChildren extends StatefulWidget {
418424
final LayoutProperties? highlighted;
419425
final ScrollController scrollController;
420426
final Axis direction;
421-
final FlexLayoutExplorerWidgetState state;
422427

423428
@override
424429
State<VisualizeFlexChildren> createState() => _VisualizeFlexChildrenState();
@@ -499,7 +504,6 @@ class _VisualizeFlexChildrenState extends State<VisualizeFlexChildren> {
499504

500505
final visualizer = FlexChildVisualizer(
501506
key: isSelected ? selectedChildKey : null,
502-
state: widget.state,
503507
layoutProperties: child,
504508
isSelected: isSelected,
505509
renderProperties: renderProperties[i],
@@ -573,44 +577,39 @@ class _VisualizeFlexChildrenState extends State<VisualizeFlexChildren> {
573577
class FlexChildVisualizer extends StatelessWidget {
574578
const FlexChildVisualizer({
575579
super.key,
576-
required this.state,
577580
required this.layoutProperties,
578581
required this.renderProperties,
579582
required this.isSelected,
580583
});
581584

582-
final FlexLayoutExplorerWidgetState state;
583-
584585
final bool isSelected;
585586

586587
final LayoutProperties layoutProperties;
587588

588589
final RenderProperties renderProperties;
589590

590-
// TODO(polina-c, jacob314): consider refactoring to remove `!`.
591-
FlexLayoutProperties get root => state.properties!;
592-
593591
LayoutProperties get properties => renderProperties.layoutProperties;
594592

595593
ObjectGroup? get objectGroup =>
596594
properties.node.objectGroupApi as ObjectGroup?;
597595

598-
void onChangeFlexFactor(int? newFlexFactor) async {
599-
state.markAsDirty();
596+
void _onChangeFlexFactor(int? newFlexFactor, VoidCallback markAsDirty) async {
597+
markAsDirty();
600598
await objectGroup!.invokeSetFlexFactor(
601599
properties.node.valueRef,
602600
newFlexFactor,
603601
);
604602
}
605603

606-
void onChangeFlexFit(FlexFit? newFlexFit) async {
607-
state.markAsDirty();
604+
void _onChangeFlexFit(FlexFit? newFlexFit, VoidCallback markAsDirty) async {
605+
markAsDirty();
608606
await objectGroup!.invokeSetFlexFit(properties.node.valueRef, newFlexFit!);
609607
}
610608

611609
Widget _buildFlexFactorChangerDropdown(
612610
int maximumFlexFactor,
613611
ThemeData theme,
612+
VoidCallback markAsDirty,
614613
) {
615614
final propertiesLocal = properties;
616615

@@ -632,7 +631,7 @@ class FlexChildVisualizer extends StatelessWidget {
632631

633632
return DropdownButton<int>(
634633
value: propertiesLocal.flexFactor?.toInt().clamp(0, maximumFlexFactor),
635-
onChanged: onChangeFlexFactor,
634+
onChanged: (newFactor) => _onChangeFlexFactor(newFactor, markAsDirty),
636635
iconEnabledColor: textColor,
637636
underline: buildUnderline(),
638637
items: <DropdownMenuItem<int>>[
@@ -642,7 +641,10 @@ class FlexChildVisualizer extends StatelessWidget {
642641
);
643642
}
644643

645-
Widget _buildFlexFitChangerDropdown(ThemeData theme) {
644+
Widget _buildFlexFitChangerDropdown(
645+
ThemeData theme,
646+
VoidCallback markAsDirty,
647+
) {
646648
Widget flexFitDescription(FlexFit flexFit) => Text(
647649
'fit: ${flexFit.name}',
648650
style: theme.regularTextStyleWithColor(emphasizedTextColor),
@@ -664,7 +666,7 @@ class FlexChildVisualizer extends StatelessWidget {
664666

665667
return DropdownButton<FlexFit>(
666668
value: propertiesLocal.flexFit,
667-
onChanged: onChangeFlexFit,
669+
onChanged: (newFit) => _onChangeFlexFit(newFit, markAsDirty),
668670
underline: buildUnderline(),
669671
iconEnabledColor: emphasizedTextColor,
670672
items: <DropdownMenuItem<FlexFit>>[
@@ -675,7 +677,7 @@ class FlexChildVisualizer extends StatelessWidget {
675677
);
676678
}
677679

678-
Widget _buildContent(ThemeData theme) {
680+
Widget _buildContent(ThemeData theme, FlexLayoutExplorerScope scope) {
679681
// TODO(https://github.com/flutter/devtools/issues/4058) allow more dynamic
680682
// flex factor input
681683
final currentFlexFactor = properties.flexFactor?.toInt() ?? 0;
@@ -690,11 +692,15 @@ class FlexChildVisualizer extends StatelessWidget {
690692
crossAxisAlignment: CrossAxisAlignment.end,
691693
children: [
692694
Flexible(
693-
child: _buildFlexFactorChangerDropdown(currentMaxFlexFactor, theme),
695+
child: _buildFlexFactorChangerDropdown(
696+
currentMaxFlexFactor,
697+
theme,
698+
scope.markAsDirty,
699+
),
694700
),
695701
if (!properties.hasFlexFactor)
696702
Text(
697-
'unconstrained ${root.isMainAxisHorizontal ? 'horizontal' : 'vertical'}',
703+
'unconstrained ${scope.rootProperties.isMainAxisHorizontal ? 'horizontal' : 'vertical'}',
698704
style: theme.regularTextStyle.copyWith(
699705
color: theme.colorScheme.unconstrainedColor,
700706
fontStyle: FontStyle.italic,
@@ -705,18 +711,19 @@ class FlexChildVisualizer extends StatelessWidget {
705711
textScaler: const TextScaler.linear(smallTextScaleFactor),
706712
textAlign: TextAlign.center,
707713
),
708-
_buildFlexFitChangerDropdown(theme),
714+
_buildFlexFitChangerDropdown(theme, scope.markAsDirty),
709715
],
710716
),
711717
);
712718
}
713719

714720
@override
715721
Widget build(BuildContext context) {
722+
final scope = FlexLayoutExplorerScope.of(context);
716723
final renderSize = renderProperties.size;
717724
final renderOffset = renderProperties.offset;
718725
final propertiesLocal = properties;
719-
final rootLocal = root;
726+
final rootLocal = scope.rootProperties;
720727

721728
Widget buildEntranceAnimation(BuildContext _, Widget? child) {
722729
final vertical = rootLocal.isMainAxisVertical;
@@ -730,11 +737,11 @@ class FlexChildVisualizer extends StatelessWidget {
730737
vertical ? minRenderHeight - entranceMargin : renderSize.height,
731738
),
732739
end: renderSize,
733-
).evaluate(state.entranceCurve)!
740+
).evaluate(scope.entranceCurve)!
734741
: renderSize;
735742
// Not-expanded widgets enter much faster.
736743
return Opacity(
737-
opacity: min([state.entranceCurve.value * 5, 1.0]),
744+
opacity: min([scope.entranceCurve.value * 5, 1.0]),
738745
child: Padding(
739746
padding: EdgeInsets.symmetric(
740747
horizontal: math.max(0.0, (renderSize.width - size.width) / 2),
@@ -749,14 +756,14 @@ class FlexChildVisualizer extends StatelessWidget {
749756
top: renderOffset.dy,
750757
left: renderOffset.dx,
751758
child: GestureDetector(
752-
onTap: () => unawaited(state.onTap(propertiesLocal)),
753-
onDoubleTap: () => state.onDoubleTap(propertiesLocal),
754-
onLongPress: () => state.onDoubleTap(propertiesLocal),
759+
onTap: () => unawaited(scope.onTap(propertiesLocal)),
760+
onDoubleTap: () => scope.onDoubleTap(propertiesLocal),
761+
onLongPress: () => scope.onDoubleTap(propertiesLocal),
755762
child: SizedBox(
756763
width: renderSize.width,
757764
height: renderSize.height,
758765
child: AnimatedBuilder(
759-
animation: state.entranceController,
766+
animation: scope.entranceController,
760767
builder: buildEntranceAnimation,
761768
child: WidgetVisualizer(
762769
isFlex: true,
@@ -769,7 +776,7 @@ class FlexChildVisualizer extends StatelessWidget {
769776
properties: propertiesLocal,
770777
child: Align(
771778
alignment: Alignment.topRight,
772-
child: _buildContent(Theme.of(context)),
779+
child: _buildContent(Theme.of(context), scope),
773780
),
774781
),
775782
),
@@ -783,3 +790,54 @@ class FlexChildVisualizer extends StatelessWidget {
783790
/// for example if it's set to 5 the dropdown will consist of 6 items (null and 0..5)
784791
static const maximumFlexFactorOptions = 5;
785792
}
793+
794+
/// Scope providing [FlexLayoutProperties] and callbacks for
795+
/// [FlexLayoutExplorerWidget] descendants.
796+
class FlexLayoutExplorerScope extends InheritedWidget {
797+
const FlexLayoutExplorerScope({
798+
super.key,
799+
required this.rootProperties,
800+
required this.onTap,
801+
required this.onDoubleTap,
802+
required this.markAsDirty,
803+
required this.entranceController,
804+
required this.entranceCurve,
805+
required super.child,
806+
});
807+
808+
/// The properties of the root flex widget being inspected.
809+
final FlexLayoutProperties rootProperties;
810+
811+
/// Callback when a child widget is tapped.
812+
final Future<void> Function(LayoutProperties properties) onTap;
813+
814+
/// Callback when a child widget is double tapped.
815+
final void Function(LayoutProperties properties) onDoubleTap;
816+
817+
/// Callback to mark the layout explorer as dirty for a future refresh.
818+
final VoidCallback markAsDirty;
819+
820+
/// The animation controller for the entrance animation.
821+
final AnimationController entranceController;
822+
823+
/// The curved animation for the entrance animation.
824+
final CurvedAnimation entranceCurve;
825+
826+
/// Retrieves the nearest [FlexLayoutExplorerScope] ancestor.
827+
static FlexLayoutExplorerScope of(BuildContext context) {
828+
final scope = context
829+
.dependOnInheritedWidgetOfExactType<FlexLayoutExplorerScope>();
830+
assert(scope != null, 'No FlexLayoutExplorerScope found in context');
831+
return scope!;
832+
}
833+
834+
@override
835+
bool updateShouldNotify(FlexLayoutExplorerScope oldWidget) {
836+
return rootProperties != oldWidget.rootProperties ||
837+
onTap != oldWidget.onTap ||
838+
onDoubleTap != oldWidget.onDoubleTap ||
839+
markAsDirty != oldWidget.markAsDirty ||
840+
entranceController != oldWidget.entranceController ||
841+
entranceCurve != oldWidget.entranceCurve;
842+
}
843+
}

0 commit comments

Comments
 (0)