Skip to content

Commit bff9f44

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 bff9f44

1 file changed

Lines changed: 99 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: 99 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,58 @@ 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+
required FlexLayoutProperties rootProperties,
799+
required Future<void> Function(LayoutProperties properties) onTap,
800+
required void Function(LayoutProperties properties) onDoubleTap,
801+
required VoidCallback markAsDirty,
802+
required AnimationController entranceController,
803+
required CurvedAnimation entranceCurve,
804+
required super.child,
805+
}) : _rootProperties = rootProperties,
806+
_onTap = onTap,
807+
_onDoubleTap = onDoubleTap,
808+
_markAsDirty = markAsDirty,
809+
_entranceController = entranceController,
810+
_entranceCurve = entranceCurve;
811+
812+
/// The properties of the root flex widget being inspected.
813+
final FlexLayoutProperties _rootProperties;
814+
815+
/// Callback when a child widget is tapped.
816+
final Future<void> Function(LayoutProperties properties) _onTap;
817+
818+
/// Callback when a child widget is double tapped.
819+
final void Function(LayoutProperties properties) _onDoubleTap;
820+
821+
/// Callback to mark the layout explorer as dirty for a future refresh.
822+
final VoidCallback _markAsDirty;
823+
824+
/// The animation controller for the entrance animation.
825+
final AnimationController _entranceController;
826+
827+
/// The curved animation for the entrance animation.
828+
final CurvedAnimation _entranceCurve;
829+
830+
/// Retrieves the nearest [_FlexLayoutExplorerScope] ancestor.
831+
static _FlexLayoutExplorerScope _of(BuildContext context) {
832+
final scope = context
833+
.dependOnInheritedWidgetOfExactType<_FlexLayoutExplorerScope>();
834+
assert(scope != null, 'No _FlexLayoutExplorerScope found in context');
835+
return scope!;
836+
}
837+
838+
@override
839+
bool updateShouldNotify(_FlexLayoutExplorerScope oldWidget) {
840+
return _rootProperties != oldWidget._rootProperties ||
841+
_onTap != oldWidget._onTap ||
842+
_onDoubleTap != oldWidget._onDoubleTap ||
843+
_markAsDirty != oldWidget._markAsDirty ||
844+
_entranceController != oldWidget._entranceController ||
845+
_entranceCurve != oldWidget._entranceCurve;
846+
}
847+
}

0 commit comments

Comments
 (0)