diff --git a/dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart index 1fbe805ea01..dcd2e687b0f 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart @@ -210,8 +210,8 @@ class _ExpansionPanelsDemoState extends State { builder: (BuildContext context) { return CollapsibleBody( margin: const EdgeInsets.symmetric(horizontal: 16.0), - onSave: () { Form.of(context)!.save(); close(); }, - onCancel: () { Form.of(context)!.reset(); close(); }, + onSave: () { Form.of(context).save(); close(); }, + onCancel: () { Form.of(context).reset(); close(); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: TextFormField( @@ -244,8 +244,8 @@ class _ExpansionPanelsDemoState extends State { child: Builder( builder: (BuildContext context) { return CollapsibleBody( - onSave: () { Form.of(context)!.save(); close(); }, - onCancel: () { Form.of(context)!.reset(); close(); }, + onSave: () { Form.of(context).save(); close(); }, + onCancel: () { Form.of(context).reset(); close(); }, child: FormField( initialValue: item.value, onSaved: (Location? result) { item.value = result; }, @@ -298,8 +298,8 @@ class _ExpansionPanelsDemoState extends State { child: Builder( builder: (BuildContext context) { return CollapsibleBody( - onSave: () { Form.of(context)!.save(); close(); }, - onCancel: () { Form.of(context)!.reset(); close(); }, + onSave: () { Form.of(context).save(); close(); }, + onCancel: () { Form.of(context).reset(); close(); }, child: FormField( initialValue: item.value, onSaved: (double? value) { item.value = value; }, diff --git a/dev/integration_tests/flutter_gallery/lib/demo/material/page_selector_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/material/page_selector_demo.dart index dc8a068f99d..320fc62d0db 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/material/page_selector_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/material/page_selector_demo.dart @@ -12,7 +12,7 @@ class _PageSelector extends StatelessWidget { final List? icons; void _handleArrowButtonPress(BuildContext context, int delta) { - final TabController controller = DefaultTabController.of(context)!; + final TabController controller = DefaultTabController.of(context); if (!controller.indexIsChanging) { controller.animateTo((controller.index + delta).clamp(0, icons!.length - 1)); } @@ -20,7 +20,7 @@ class _PageSelector extends StatelessWidget { @override Widget build(BuildContext context) { - final TabController? controller = DefaultTabController.of(context); + final TabController? controller = DefaultTabController.maybeOf(context); final Color color = Theme.of(context).colorScheme.secondary; return SafeArea( top: false, diff --git a/dev/integration_tests/flutter_gallery/lib/gallery/demo.dart b/dev/integration_tests/flutter_gallery/lib/gallery/demo.dart index fe29b67dc00..30acad6b3d1 100644 --- a/dev/integration_tests/flutter_gallery/lib/gallery/demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/gallery/demo.dart @@ -63,7 +63,7 @@ class TabbedComponentDemoScaffold extends StatefulWidget { class _TabbedComponentDemoScaffoldState extends State { void _showExampleCode(BuildContext context) { - final String? tag = widget.demos![DefaultTabController.of(context)!.index].exampleCodeTag; + final String? tag = widget.demos![DefaultTabController.of(context).index].exampleCodeTag; if (tag != null) { Navigator.push(context, MaterialPageRoute( builder: (BuildContext context) => FullScreenCodeDialog(exampleCodeTag: tag) @@ -72,7 +72,7 @@ class _TabbedComponentDemoScaffoldState extends State _showApiDocumentation(BuildContext context) async { - final String? url = widget.demos![DefaultTabController.of(context)!.index].documentationUrl; + final String? url = widget.demos![DefaultTabController.of(context).index].documentationUrl; if (url == null) { return; } diff --git a/dev/manual_tests/lib/material_arc.dart b/dev/manual_tests/lib/material_arc.dart index ccffb75294d..70b686bb51e 100644 --- a/dev/manual_tests/lib/material_arc.dart +++ b/dev/manual_tests/lib/material_arc.dart @@ -451,7 +451,7 @@ class _AnimationDemoState extends State with TickerProviderStateM return FloatingActionButton( child: const Icon(Icons.refresh), onPressed: () { - _play(_allDemos[DefaultTabController.of(context)!.index]); + _play(_allDemos[DefaultTabController.of(context).index]); }, ); }, diff --git a/dev/manual_tests/lib/overlay_geometry.dart b/dev/manual_tests/lib/overlay_geometry.dart index 5fb9d8b97d1..813b31303c0 100644 --- a/dev/manual_tests/lib/overlay_geometry.dart +++ b/dev/manual_tests/lib/overlay_geometry.dart @@ -172,8 +172,8 @@ class OverlayGeometryAppState extends State { markers[MarkerType.topLeft] = box!.localToGlobal(Offset.zero); final Size size = box.size; markers[MarkerType.bottomRight] = box.localToGlobal(Offset(size.width, size.height)); - final ScrollableState? scrollable = Scrollable.of(target.currentContext!); - markersScrollOffset = scrollable!.position.pixels; + final ScrollableState scrollable = Scrollable.of(target.currentContext!); + markersScrollOffset = scrollable.position.pixels; }); } diff --git a/examples/api/lib/cupertino/text_form_field_row/cupertino_text_form_field_row.1.dart b/examples/api/lib/cupertino/text_form_field_row/cupertino_text_form_field_row.1.dart index b363360343d..0e4445a1ca7 100644 --- a/examples/api/lib/cupertino/text_form_field_row/cupertino_text_form_field_row.1.dart +++ b/examples/api/lib/cupertino/text_form_field_row/cupertino_text_form_field_row.1.dart @@ -34,7 +34,7 @@ class FromSectionExample extends StatelessWidget { child: Form( autovalidateMode: AutovalidateMode.always, onChanged: () { - Form.of(primaryFocus!.context!)?.save(); + Form.maybeOf(primaryFocus!.context!)?.save(); }, child: CupertinoFormSection.insetGrouped( header: const Text('SECTION 1'), diff --git a/examples/api/lib/material/tab_controller/tab_controller.1.dart b/examples/api/lib/material/tab_controller/tab_controller.1.dart index 15cddf479f0..ee44def2f13 100644 --- a/examples/api/lib/material/tab_controller/tab_controller.1.dart +++ b/examples/api/lib/material/tab_controller/tab_controller.1.dart @@ -38,7 +38,7 @@ class MyStatelessWidget extends StatelessWidget { // The Builder widget is used to have a different BuildContext to access // closest DefaultTabController. child: Builder(builder: (BuildContext context) { - final TabController tabController = DefaultTabController.of(context)!; + final TabController tabController = DefaultTabController.of(context); tabController.addListener(() { if (!tabController.indexIsChanging) { // Your code goes here. diff --git a/examples/api/lib/material/text_form_field/text_form_field.1.dart b/examples/api/lib/material/text_form_field/text_form_field.1.dart index 65ac51d29ee..979965b5e34 100644 --- a/examples/api/lib/material/text_form_field/text_form_field.1.dart +++ b/examples/api/lib/material/text_form_field/text_form_field.1.dart @@ -44,7 +44,7 @@ class _MyStatefulWidgetState extends State { child: Form( autovalidateMode: AutovalidateMode.always, onChanged: () { - Form.of(primaryFocus!.context!)!.save(); + Form.of(primaryFocus!.context!).save(); }, child: Wrap( children: List.generate(5, (int index) { diff --git a/packages/flutter/lib/src/cupertino/page_scaffold.dart b/packages/flutter/lib/src/cupertino/page_scaffold.dart index 1aec6279b60..983cac302c2 100644 --- a/packages/flutter/lib/src/cupertino/page_scaffold.dart +++ b/packages/flutter/lib/src/cupertino/page_scaffold.dart @@ -90,7 +90,7 @@ class CupertinoPageScaffold extends StatefulWidget { class _CupertinoPageScaffoldState extends State { void _handleStatusBarTap() { - final ScrollController? primaryScrollController = PrimaryScrollController.of(context); + final ScrollController? primaryScrollController = PrimaryScrollController.maybeOf(context); // Only act on the scroll controller if it has any attached scroll positions. if (primaryScrollController != null && primaryScrollController.hasClients) { primaryScrollController.animateTo( diff --git a/packages/flutter/lib/src/material/about.dart b/packages/flutter/lib/src/material/about.dart index a8711a076b9..3b8e7cc32ed 100644 --- a/packages/flutter/lib/src/material/about.dart +++ b/packages/flutter/lib/src/material/about.dart @@ -602,7 +602,7 @@ class _PackagesViewState extends State<_PackagesView> { } final String packageName = data.packages[widget.selectedId.value ?? 0]; final List bindings = data.packageLicenseBindings[packageName]!; - _MasterDetailFlow.of(context)!.setInitialDetailPage( + _MasterDetailFlow.of(context).setInitialDetailPage( _DetailArguments( packageName, bindings.map((int i) => data.licenses[i]).toList(growable: false), @@ -632,7 +632,7 @@ class _PackagesViewState extends State<_PackagesView> { numberLicenses: bindings.length, onTap: () { widget.selectedId.value = packageIndex; - _MasterDetailFlow.of(context)!.openDetailPage(_DetailArguments( + _MasterDetailFlow.of(context).openDetailPage(_DetailArguments( packageName, bindings.map((int i) => data.licenses[i]).toList(growable: false), )); @@ -1073,7 +1073,7 @@ class _MasterDetailFlow extends StatefulWidget { // ```dart // _MasterDetailFlow.of(context).openDetailPage(arguments); // ``` - static _MasterDetailFlowProxy? of(BuildContext context) { + static _MasterDetailFlowProxy of(BuildContext context) { _PageOpener? pageOpener = context.findAncestorStateOfType<_MasterDetailScaffoldState>(); pageOpener ??= context.findAncestorStateOfType<_MasterDetailFlowState>(); assert(() { @@ -1086,7 +1086,7 @@ class _MasterDetailFlow extends StatefulWidget { } return true; }()); - return pageOpener != null ? _MasterDetailFlowProxy._(pageOpener) : null; + return _MasterDetailFlowProxy._(pageOpener!); } } @@ -1339,13 +1339,13 @@ class _MasterDetailScaffoldState extends State<_MasterDetailScaffold> @override void openDetailPage(Object arguments) { SchedulerBinding.instance.addPostFrameCallback((_) => _detailArguments.value = arguments); - _MasterDetailFlow.of(context)!.openDetailPage(arguments); + _MasterDetailFlow.of(context).openDetailPage(arguments); } @override void setInitialDetailPage(Object arguments) { SchedulerBinding.instance.addPostFrameCallback((_) => _detailArguments.value = arguments); - _MasterDetailFlow.of(context)!.setInitialDetailPage(arguments); + _MasterDetailFlow.of(context).setInitialDetailPage(arguments); } @override diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index c0db39e95ad..7ca340e7369 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -820,13 +820,9 @@ class _AppBarState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - if (_scrollNotificationObserver != null) { - _scrollNotificationObserver!.removeListener(_handleScrollNotification); - } - _scrollNotificationObserver = ScrollNotificationObserver.of(context); - if (_scrollNotificationObserver != null) { - _scrollNotificationObserver!.addListener(_handleScrollNotification); - } + _scrollNotificationObserver?.removeListener(_handleScrollNotification); + _scrollNotificationObserver = ScrollNotificationObserver.maybeOf(context); + _scrollNotificationObserver?.addListener(_handleScrollNotification); } @override diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart index 9b24edd7fe9..2e9ac8264a6 100644 --- a/packages/flutter/lib/src/material/calendar_date_picker.dart +++ b/packages/flutter/lib/src/material/calendar_date_picker.dart @@ -806,7 +806,7 @@ class _FocusedDate extends InheritedWidget { return !DateUtils.isSameDay(date, oldWidget.date); } - static DateTime? of(BuildContext context) { + static DateTime? maybeOf(BuildContext context) { final _FocusedDate? focusedDate = context.dependOnInheritedWidgetOfExactType<_FocusedDate>(); return focusedDate?.date; } @@ -887,7 +887,7 @@ class _DayPickerState extends State<_DayPicker> { void didChangeDependencies() { super.didChangeDependencies(); // Check to see if the focused date is in this month, if so focus it. - final DateTime? focusedDate = _FocusedDate.of(context); + final DateTime? focusedDate = _FocusedDate.maybeOf(context); if (focusedDate != null && DateUtils.isSameMonth(widget.displayedMonth, focusedDate)) { _dayFocusNodes[focusedDate.day - 1].requestFocus(); } diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 4823a1fed68..32cbcf10b52 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -1940,12 +1940,11 @@ class _FocusedDate extends InheritedWidget { return !DateUtils.isSameDay(date, oldWidget.date) || scrollDirection != oldWidget.scrollDirection; } - static _FocusedDate? of(BuildContext context) { + static _FocusedDate? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<_FocusedDate>(); } } - class _DayHeaders extends StatelessWidget { const _DayHeaders(); @@ -2217,7 +2216,7 @@ class _MonthItemState extends State<_MonthItem> { void didChangeDependencies() { super.didChangeDependencies(); // Check to see if the focused date is in this month, if so focus it. - final DateTime? focusedDate = _FocusedDate.of(context)?.date; + final DateTime? focusedDate = _FocusedDate.maybeOf(context)?.date; if (focusedDate != null && DateUtils.isSameMonth(widget.displayedMonth, focusedDate)) { _dayFocusNodes[focusedDate.day - 1].requestFocus(); } @@ -2237,7 +2236,7 @@ class _MonthItemState extends State<_MonthItem> { void _dayFocusChanged(bool focused) { if (focused) { - final TraversalDirection? focusDirection = _FocusedDate.of(context)?.scrollDirection; + final TraversalDirection? focusDirection = _FocusedDate.maybeOf(context)?.scrollDirection; if (focusDirection != null) { ScrollPositionAlignmentPolicy policy = ScrollPositionAlignmentPolicy.explicit; switch (focusDirection) { diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index 3dc38c5250a..74596677e17 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -332,7 +332,7 @@ class _ExpansionTileState extends State with SingleTickerProvider _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween)); - _isExpanded = PageStorage.of(context)?.readState(context) as bool? ?? widget.initiallyExpanded; + _isExpanded = PageStorage.maybeOf(context)?.readState(context) as bool? ?? widget.initiallyExpanded; if (_isExpanded) { _controller.value = 1.0; } @@ -359,7 +359,7 @@ class _ExpansionTileState extends State with SingleTickerProvider }); }); } - PageStorage.of(context)?.writeState(context, _isExpanded); + PageStorage.maybeOf(context)?.writeState(context, _isExpanded); }); widget.onExpansionChanged?.call(_isExpanded); } diff --git a/packages/flutter/lib/src/material/ink_decoration.dart b/packages/flutter/lib/src/material/ink_decoration.dart index ca77e765ab0..980deb598f4 100644 --- a/packages/flutter/lib/src/material/ink_decoration.dart +++ b/packages/flutter/lib/src/material/ink_decoration.dart @@ -284,7 +284,7 @@ class _InkState extends State { _ink = InkDecoration( decoration: widget.decoration, configuration: createLocalImageConfiguration(context), - controller: Material.of(context)!, + controller: Material.of(context), referenceBox: _boxKey.currentContext!.findRenderObject()! as RenderBox, onRemoved: _handleRemoved, ); diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart index 50560379738..240d199b6d5 100644 --- a/packages/flutter/lib/src/material/ink_well.dart +++ b/packages/flutter/lib/src/material/ink_well.dart @@ -197,7 +197,7 @@ class _ParentInkResponseProvider extends InheritedWidget { @override bool updateShouldNotify(_ParentInkResponseProvider oldWidget) => state != oldWidget.state; - static _ParentInkResponseState? of(BuildContext context) { + static _ParentInkResponseState? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<_ParentInkResponseProvider>()?.state; } } @@ -600,7 +600,7 @@ class InkResponse extends StatelessWidget { @override Widget build(BuildContext context) { - final _ParentInkResponseState? parentState = _ParentInkResponseProvider.of(context); + final _ParentInkResponseState? parentState = _ParentInkResponseProvider.maybeOf(context); return _InkResponseStateWidget( onTap: onTap, onTapDown: onTapDown, @@ -916,7 +916,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> } final RenderBox referenceBox = context.findRenderObject()! as RenderBox; _highlights[type] = InkHighlight( - controller: Material.of(context)!, + controller: Material.of(context), referenceBox: referenceBox, color: resolvedOverlayColor, shape: widget.highlightShape, @@ -952,7 +952,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> } InteractiveInkFeature _createInkFeature(Offset globalPosition) { - final MaterialInkController inkController = Material.of(context)!; + final MaterialInkController inkController = Material.of(context); final RenderBox referenceBox = context.findRenderObject()! as RenderBox; final Offset position = referenceBox.globalToLocal(globalPosition); final Color color = widget.overlayColor?.resolve(statesController.value) ?? widget.splashColor ?? Theme.of(context).splashColor; diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index 67a696dd3de..6e2a770dea3 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -348,14 +348,55 @@ class Material extends StatefulWidget { /// Typical usage is as follows: /// /// ```dart - /// MaterialInkController? inkController = Material.of(context); + /// MaterialInkController? inkController = Material.maybeOf(context); /// ``` /// /// This method can be expensive (it walks the element tree). - static MaterialInkController? of(BuildContext context) { + /// + /// See also: + /// + /// * [Material.of], which is similar to this method, but asserts if + /// no [Material] ancestor is found. + static MaterialInkController? maybeOf(BuildContext context) { return context.findAncestorRenderObjectOfType<_RenderInkFeatures>(); } + /// The ink controller from the closest instance of [Material] that encloses + /// the given context. + /// + /// If no [Material] widget ancestor can be found then this method will assert + /// in debug mode, and throw an exception in release mode. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// MaterialInkController inkController = Material.of(context); + /// ``` + /// + /// This method can be expensive (it walks the element tree). + /// + /// See also: + /// + /// * [Material.maybeOf], which is similar to this method, but returns null if + /// no [Material] ancestor is found. + static MaterialInkController of(BuildContext context) { + final MaterialInkController? controller = maybeOf(context); + assert(() { + if (controller == null) { + throw FlutterError( + 'Material.of() was called with a context that does not contain a Material widget.\n' + 'No Material widget ancestor could be found starting from the context that was passed to ' + 'Material.of(). This can happen because you are using a widget that looks for a Material ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return controller!; + } + @override State createState() => _MaterialState(); diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 84e624044d5..b2f32163dd5 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -305,7 +305,7 @@ class _MenuAnchorState extends State { _parent = _MenuAnchorState._maybeOf(context); _parent?._addChild(this); _position?.isScrollingNotifier.removeListener(_handleScroll); - _position = Scrollable.of(context)?.position; + _position = Scrollable.maybeOf(context)?.position; _position?.isScrollingNotifier.addListener(_handleScroll); final Size newSize = MediaQuery.of(context).size; if (_viewSize != null && newSize != _viewSize) { diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart index 64ba4aa8598..aa7cb626600 100644 --- a/packages/flutter/lib/src/material/paginated_data_table.dart +++ b/packages/flutter/lib/src/material/paginated_data_table.dart @@ -267,7 +267,7 @@ class PaginatedDataTableState extends State { @override void initState() { super.initState(); - _firstRowIndex = PageStorage.of(context)?.readState(context) as int? ?? widget.initialFirstRowIndex ?? 0; + _firstRowIndex = PageStorage.maybeOf(context)?.readState(context) as int? ?? widget.initialFirstRowIndex ?? 0; widget.source.addListener(_handleDataSourceChanged); _handleDataSourceChanged(); } diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 07e69bba701..12af8c6801c 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -2536,7 +2536,7 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto // top. We implement this by looking up the primary scroll controller and // scrolling it to the top when tapped. void _handleStatusBarTap() { - final ScrollController? primaryScrollController = PrimaryScrollController.of(context); + final ScrollController? primaryScrollController = PrimaryScrollController.maybeOf(context); if (primaryScrollController != null && primaryScrollController.hasClients) { primaryScrollController.animateTo( 0.0, diff --git a/packages/flutter/lib/src/material/tab_controller.dart b/packages/flutter/lib/src/material/tab_controller.dart index b9d9f5a1c49..e6b1b84740e 100644 --- a/packages/flutter/lib/src/material/tab_controller.dart +++ b/packages/flutter/lib/src/material/tab_controller.dart @@ -375,18 +375,65 @@ class DefaultTabController extends StatefulWidget { /// {@macro flutter.widgets.ProxyWidget.child} final Widget child; - /// The closest instance of this class that encloses the given context. + /// The closest instance of [DefaultTabController] that encloses the given + /// context, or null if none is found. /// - /// {@tool snippet} - /// Typical usage is as follows: + /// {@tool snippet} Typical usage is as follows: /// /// ```dart - /// TabController controller = DefaultTabController.of(context)!; + /// TabController? controller = DefaultTabController.maybeOf(context); /// ``` /// {@end-tool} - static TabController? of(BuildContext context) { - final _TabControllerScope? scope = context.dependOnInheritedWidgetOfExactType<_TabControllerScope>(); - return scope?.controller; + /// + /// Calling this method will create a dependency on the closest + /// [DefaultTabController] in the [context], if there is one. + /// + /// See also: + /// + /// * [DefaultTabController.of], which is similar to this method, but asserts + /// if no [DefaultTabController] ancestor is found. + static TabController? maybeOf(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType<_TabControllerScope>()?.controller; + } + + /// The closest instance of [DefaultTabController] that encloses the given + /// context. + /// + /// If no instance is found, this method will assert in debug mode and throw + /// an exception in release mode. + /// + /// Calling this method will create a dependency on the closest + /// [DefaultTabController] in the [context]. + /// + /// {@tool snippet} Typical usage is as follows: + /// + /// ```dart + /// TabController controller = DefaultTabController.of(context); + /// ``` + /// {@end-tool} + /// + /// See also: + /// + /// * [DefaultTabController.maybeOf], which is similar to this method, but + /// returns null if no [DefaultTabController] ancestor is found. + static TabController of(BuildContext context) { + final TabController? controller = maybeOf(context); + assert(() { + if (controller == null) { + throw FlutterError( + 'DefaultTabController.of() was called with a context that does not ' + 'contain a DefaultTabController widget.\n' + 'No DefaultTabController widget ancestor could be found starting from ' + 'the context that was passed to DefaultTabController.of(). This can ' + 'happen because you are using a widget that looks for a DefaultTabController ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return controller!; } @override diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 9e03866c3e9..7602531f6d6 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -954,7 +954,7 @@ class _TabBarState extends State { // TODO(xu-baolin): Remove automatic adjustment to white color indicator // with a better long-term solution. // https://github.com/flutter/flutter/pull/68171#pullrequestreview-517753917 - if (widget.automaticIndicatorColorAdjustment && color.value == Material.of(context)?.color?.value) { + if (widget.automaticIndicatorColorAdjustment && color.value == Material.maybeOf(context)?.color?.value) { color = Colors.white; } @@ -972,7 +972,7 @@ class _TabBarState extends State { bool get _controllerIsValid => _controller?.animation != null; void _updateTabController() { - final TabController? newController = widget.controller ?? DefaultTabController.of(context); + final TabController? newController = widget.controller ?? DefaultTabController.maybeOf(context); assert(() { if (newController == null) { throw FlutterError( @@ -1411,7 +1411,7 @@ class _TabBarViewState extends State { bool get _controllerIsValid => _controller?.animation != null; void _updateTabController() { - final TabController? newController = widget.controller ?? DefaultTabController.of(context); + final TabController? newController = widget.controller ?? DefaultTabController.maybeOf(context); assert(() { if (newController == null) { throw FlutterError( @@ -1768,7 +1768,7 @@ class TabPageSelector extends StatelessWidget { final Color fixSelectedColor = selectedColor ?? Theme.of(context).colorScheme.secondary; final ColorTween selectedColorTween = ColorTween(begin: fixColor, end: fixSelectedColor); final ColorTween previousColorTween = ColorTween(begin: fixSelectedColor, end: fixColor); - final TabController? tabController = controller ?? DefaultTabController.of(context); + final TabController? tabController = controller ?? DefaultTabController.maybeOf(context); final MaterialLocalizations localizations = MaterialLocalizations.of(context); assert(() { if (tabController == null) { diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index c48466b9166..8913deb2528 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -38,7 +38,12 @@ abstract class RenderAbstractViewport extends RenderObject { /// /// If the object does not have a [RenderAbstractViewport] as an ancestor, /// this function returns null. - static RenderAbstractViewport? of(RenderObject? object) { + /// + /// See also: + /// + /// * [RenderAbstractViewport.of], which is similar to this method, but + /// asserts if no [RenderAbstractViewport] ancestor is found. + static RenderAbstractViewport? maybeOf(RenderObject? object) { while (object != null) { if (object is RenderAbstractViewport) { return object; @@ -48,6 +53,35 @@ abstract class RenderAbstractViewport extends RenderObject { return null; } + /// Returns the [RenderAbstractViewport] that most tightly encloses the given + /// render object. + /// + /// If the object does not have a [RenderAbstractViewport] as an ancestor, + /// this function will assert in debug mode, and throw an exception in release + /// mode. + /// + /// See also: + /// + /// * [RenderAbstractViewport.maybeOf], which is similar to this method, but + /// returns null if no [RenderAbstractViewport] ancestor is found. + static RenderAbstractViewport of(RenderObject? object) { + final RenderAbstractViewport? viewport = maybeOf(object); + assert(() { + if (viewport == null) { + throw FlutterError( + 'RenderAbstractViewport.of() was called with a render object that was ' + 'not a descendant of a RenderAbstractViewport.\n' + 'No RenderAbstractViewport render object ancestor could be found starting ' + 'from the object that was passed to RenderAbstractViewport.of().\n' + 'The render object where the viewport search started was:\n' + ' $object', + ); + } + return true; + }()); + return viewport!; + } + /// Returns the offset that would be needed to reveal the `target` /// [RenderObject]. /// diff --git a/packages/flutter/lib/src/widgets/autofill.dart b/packages/flutter/lib/src/widgets/autofill.dart index 570482cb082..a01e9762a9d 100644 --- a/packages/flutter/lib/src/widgets/autofill.dart +++ b/packages/flutter/lib/src/widgets/autofill.dart @@ -72,19 +72,62 @@ class AutofillGroup extends StatefulWidget { this.onDisposeAction = AutofillContextAction.commit, }) : assert(child != null); - /// Returns the closest [AutofillGroupState] which encloses the given context. + /// Returns the [AutofillGroupState] of the closest [AutofillGroup] widget + /// which encloses the given context, or null if one cannot be found. + /// + /// Calling this method will create a dependency on the closest + /// [AutofillGroup] in the [context], if there is one. /// /// {@macro flutter.widgets.AutofillGroupState} /// /// See also: /// + /// * [AutofillGroup.of], which is similar to this method, but asserts if an + /// [AutofillGroup] cannot be found. /// * [EditableTextState], where this method is used to retrieve the closest /// [AutofillGroupState]. - static AutofillGroupState? of(BuildContext context) { + static AutofillGroupState? maybeOf(BuildContext context) { final _AutofillScope? scope = context.dependOnInheritedWidgetOfExactType<_AutofillScope>(); return scope?._scope; } + /// Returns the [AutofillGroupState] of the closest [AutofillGroup] widget + /// which encloses the given context. + /// + /// If no instance is found, this method will assert in debug mode and throw + /// an exception in release mode. + /// + /// Calling this method will create a dependency on the closest + /// [AutofillGroup] in the [context]. + /// + /// {@macro flutter.widgets.AutofillGroupState} + /// + /// See also: + /// + /// * [AutofillGroup.maybeOf], which is similar to this method, but returns + /// null if an [AutofillGroup] cannot be found. + /// * [EditableTextState], where this method is used to retrieve the closest + /// [AutofillGroupState]. + static AutofillGroupState of(BuildContext context) { + final AutofillGroupState? groupState = maybeOf(context); + assert(() { + if (groupState == null) { + throw FlutterError( + 'AutofillGroup.of() was called with a context that does not contain an ' + 'AutofillGroup widget.\n' + 'No AutofillGroup widget ancestor could be found starting from the ' + 'context that was passed to AutofillGroup.of(). This can happen ' + 'because you are using a widget that looks for an AutofillGroup ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return groupState!; + } + /// {@macro flutter.widgets.ProxyWidget.child} final Widget child; @@ -171,7 +214,7 @@ class AutofillGroupState extends State with AutofillScopeMixin { @override void didChangeDependencies() { super.didChangeDependencies(); - _isTopmostAutofillGroup = AutofillGroup.of(context) == null; + _isTopmostAutofillGroup = AutofillGroup.maybeOf(context) == null; } @override diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index a8a5b3bd41b..b22b27ff253 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2373,7 +2373,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien void didChangeDependencies() { super.didChangeDependencies(); - final AutofillGroupState? newAutofillGroup = AutofillGroup.of(context); + final AutofillGroupState? newAutofillGroup = AutofillGroup.maybeOf(context); if (currentAutofillScope != newAutofillGroup) { _currentAutofillScope?.unregister(autofillId); _currentAutofillScope = newAutofillGroup; @@ -3481,7 +3481,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien textAlign: widget.textAlign, textDirection: _textDirection, textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context), - textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context), + textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context), locale: widget.locale, structStyle: widget.strutStyle, placeholder: _placeholderLocation, @@ -4150,7 +4150,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien textAlign: widget.textAlign, textDirection: _textDirection, locale: widget.locale, - textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context), + textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context), textWidthBasis: widget.textWidthBasis, obscuringCharacter: widget.obscuringCharacter, obscureText: widget.obscureText, diff --git a/packages/flutter/lib/src/widgets/focus_traversal.dart b/packages/flutter/lib/src/widgets/focus_traversal.dart index a088fe2daad..7911ee898d6 100644 --- a/packages/flutter/lib/src/widgets/focus_traversal.dart +++ b/packages/flutter/lib/src/widgets/focus_traversal.dart @@ -623,7 +623,7 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { // Returns true if successfully popped the history. bool popOrInvalidate(TraversalDirection direction) { final FocusNode lastNode = policyData.history.removeLast().node; - if (Scrollable.of(lastNode.context!) != Scrollable.of(primaryFocus!.context!)) { + if (Scrollable.maybeOf(lastNode.context!) != Scrollable.maybeOf(primaryFocus!.context!)) { invalidateScopeData(nearestScope); return false; } @@ -741,7 +741,7 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { return true; } FocusNode? found; - final ScrollableState? focusedScrollable = Scrollable.of(focusedChild.context!); + final ScrollableState? focusedScrollable = Scrollable.maybeOf(focusedChild.context!); switch (direction) { case TraversalDirection.down: case TraversalDirection.up: @@ -751,7 +751,7 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { nearestScope.traversalDescendants, ); if (focusedScrollable != null && !focusedScrollable.position.atEdge) { - final Iterable filteredEligibleNodes = eligibleNodes!.where((FocusNode node) => Scrollable.of(node.context!) == focusedScrollable); + final Iterable filteredEligibleNodes = eligibleNodes!.where((FocusNode node) => Scrollable.maybeOf(node.context!) == focusedScrollable); if (filteredEligibleNodes.isNotEmpty) { eligibleNodes = filteredEligibleNodes; } @@ -783,7 +783,7 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { case TraversalDirection.left: Iterable? eligibleNodes = _sortAndFilterHorizontally(direction, focusedChild.rect, nearestScope); if (focusedScrollable != null && !focusedScrollable.position.atEdge) { - final Iterable filteredEligibleNodes = eligibleNodes!.where((FocusNode node) => Scrollable.of(node.context!) == focusedScrollable); + final Iterable filteredEligibleNodes = eligibleNodes!.where((FocusNode node) => Scrollable.maybeOf(node.context!) == focusedScrollable); if (filteredEligibleNodes.isNotEmpty) { eligibleNodes = filteredEligibleNodes; } diff --git a/packages/flutter/lib/src/widgets/form.dart b/packages/flutter/lib/src/widgets/form.dart index 4be2572eb71..24d4afb2ee1 100644 --- a/packages/flutter/lib/src/widgets/form.dart +++ b/packages/flutter/lib/src/widgets/form.dart @@ -49,20 +49,66 @@ class Form extends StatefulWidget { }) : assert(child != null), autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled; - /// Returns the closest [FormState] which encloses the given context, - /// or null if there is no such form. + /// Returns the [FormState] of the closest [Form] widget which encloses the + /// given context, or null if none is found. /// /// Typical usage is as follows: /// /// ```dart - /// FormState form = Form.of(context)!; - /// form.save(); + /// FormState? form = Form.maybeOf(context); + /// form?.save(); /// ``` - static FormState? of(BuildContext context) { + /// + /// Calling this method will create a dependency on the closest [Form] in the + /// [context], if there is one. + /// + /// See also: + /// + /// * [Form.of], which is similar to this method, but asserts if no [Form] + /// ancestor is found. + static FormState? maybeOf(BuildContext context) { final _FormScope? scope = context.dependOnInheritedWidgetOfExactType<_FormScope>(); return scope?._formState; } + /// Returns the [FormState] of the closest [Form] widget which encloses the + /// given context. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// FormState form = Form.of(context); + /// form.save(); + /// ``` + /// + /// If no [Form] ancestor is found, this will assert in debug mode, and throw + /// an exception in release mode. + /// + /// Calling this method will create a dependency on the closest [Form] in the + /// [context]. + /// + /// See also: + /// + /// * [Form.maybeOf], which is similar to this method, but returns null if no + /// [Form] ancestor is found. + static FormState of(BuildContext context) { + final FormState? formState = maybeOf(context); + assert(() { + if (formState == null) { + throw FlutterError( + 'Form.of() was called with a context that does not contain a Form widget.\n' + 'No Form widget ancestor could be found starting from the context that ' + 'was passed to Form.of(). This can happen because you are using a widget ' + 'that looks for a Form ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return formState!; + } + /// The widget below this widget in the tree. /// /// This is the root of the widget hierarchy that contains this form. @@ -380,7 +426,7 @@ class FormFieldState extends State> with RestorationMixin { _hasInteractedByUser.value = false; _errorText.value = null; }); - Form.of(context)?._fieldDidChange(); + Form.maybeOf(context)?._fieldDidChange(); } /// Calls [FormField.validator] to set the [errorText]. Returns true if there @@ -414,7 +460,7 @@ class FormFieldState extends State> with RestorationMixin { _value = value; _hasInteractedByUser.value = true; }); - Form.of(context)?._fieldDidChange(); + Form.maybeOf(context)?._fieldDidChange(); } /// Sets the value associated with this form field. @@ -441,7 +487,7 @@ class FormFieldState extends State> with RestorationMixin { @override void deactivate() { - Form.of(context)?._unregister(this); + Form.maybeOf(context)?._unregister(this); super.deactivate(); } @@ -461,7 +507,7 @@ class FormFieldState extends State> with RestorationMixin { break; } } - Form.of(context)?._register(this); + Form.maybeOf(context)?._register(this); return widget.builder(this); } } diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index bbc6e1275d9..51f2d36c43e 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -1605,8 +1605,12 @@ abstract class ParentDataWidget extends ProxyWidget { /// /// final Color color; /// +/// static FrogColor? maybeOf(BuildContext context) { +/// return context.dependOnInheritedWidgetOfExactType(); +/// } +/// /// static FrogColor of(BuildContext context) { -/// final FrogColor? result = context.dependOnInheritedWidgetOfExactType(); +/// final FrogColor? result = maybeOf(context); /// assert(result != null, 'No FrogColor found in context'); /// return result!; /// } @@ -1617,30 +1621,35 @@ abstract class ParentDataWidget extends ProxyWidget { /// ``` /// {@end-tool} /// -/// ## Implementing the `of` method +/// ## Implementing the `of` and `maybeOf` methods /// -/// The convention is to provide a static method `of` on the [InheritedWidget] -/// which does the call to [BuildContext.dependOnInheritedWidgetOfExactType]. This -/// allows the class to define its own fallback logic in case there isn't -/// a widget in scope. In the example above, the value returned will be -/// null in that case, but it could also have defaulted to a value. +/// The convention is to provide two static methods, `of` and `maybeOf`, on the +/// [InheritedWidget] which call +/// [BuildContext.dependOnInheritedWidgetOfExactType]. This allows the class to +/// define its own fallback logic in case there isn't a widget in scope. /// -/// Sometimes, the `of` method returns the data rather than the inherited -/// widget; for example, in this case it could have returned a [Color] instead -/// of the `FrogColor` widget. +/// The `of` method typically returns a non-nullable instance and asserts if the +/// [InheritedWidget] isn't found, and the `maybeOf` method returns a nullable +/// instance, and returns null if the [InheritedWidget] isn't found. The `of` +/// method is typically implemented by calling `maybeOf` internally. +/// +/// Sometimes, the `of` and `maybeOf` methods return some data rather than the +/// inherited widget itself; for example, in this case it could have returned a +/// [Color] instead of the `FrogColor` widget. /// /// Occasionally, the inherited widget is an implementation detail of another -/// class, and is therefore private. The `of` method in that case is typically -/// put on the public class instead. For example, [Theme] is implemented as a -/// [StatelessWidget] that builds a private inherited widget; [Theme.of] looks -/// for that inherited widget using [BuildContext.dependOnInheritedWidgetOfExactType] -/// and then returns the [ThemeData]. +/// class, and is therefore private. The `of` and `maybeOf` methods in that case +/// are typically implemented on the public class instead. For example, [Theme] +/// is implemented as a [StatelessWidget] that builds a private inherited +/// widget; [Theme.of] looks for that private inherited widget using +/// [BuildContext.dependOnInheritedWidgetOfExactType] and then returns the +/// [ThemeData] inside it. /// -/// ## Calling the `of` method +/// ## Calling the `of` or `maybeOf` methods /// -/// When using the `of` method, the `context` must be a descendant of the -/// [InheritedWidget], meaning it must be "below" the [InheritedWidget] in the -/// tree. +/// When using the `of` or `maybeOf` methods, the `context` must be a descendant +/// of the [InheritedWidget], meaning it must be "below" the [InheritedWidget] +/// in the tree. /// /// {@tool snippet} /// @@ -1674,8 +1683,9 @@ abstract class ParentDataWidget extends ProxyWidget { /// /// {@tool snippet} /// -/// In this example, the `context` used is the one from the `MyOtherPage` widget, -/// which is a parent of the `FrogColor` widget, so this does not work. +/// In this example, the `context` used is the one from the `MyOtherPage` +/// widget, which is a parent of the `FrogColor` widget, so this does not work, +/// and will assert when `FrogColor.of` is called. /// /// ```dart /// // continuing from previous example... @@ -1697,21 +1707,20 @@ abstract class ParentDataWidget extends ProxyWidget { /// } /// } /// ``` -/// {@end-tool} -/// {@youtube 560 315 https://www.youtube.com/watch?v=1t-8rBCGBYw} +/// {@end-tool} {@youtube 560 315 https://www.youtube.com/watch?v=1t-8rBCGBYw} /// /// See also: /// -/// * [StatefulWidget] and [State], for widgets that can build differently -/// several times over their lifetime. -/// * [StatelessWidget], for widgets that always build the same way given a -/// particular configuration and ambient state. -/// * [Widget], for an overview of widgets in general. -/// * [InheritedNotifier], an inherited widget whose value can be a -/// [Listenable], and which will notify dependents whenever the value -/// sends notifications. -/// * [InheritedModel], an inherited widget that allows clients to subscribe -/// to changes for subparts of the value. +/// * [StatefulWidget] and [State], for widgets that can build differently +/// several times over their lifetime. +/// * [StatelessWidget], for widgets that always build the same way given a +/// particular configuration and ambient state. +/// * [Widget], for an overview of widgets in general. +/// * [InheritedNotifier], an inherited widget whose value can be a +/// [Listenable], and which will notify dependents whenever the value sends +/// notifications. +/// * [InheritedModel], an inherited widget that allows clients to subscribe to +/// changes for subparts of the value. abstract class InheritedWidget extends ProxyWidget { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. diff --git a/packages/flutter/lib/src/widgets/inherited_model.dart b/packages/flutter/lib/src/widgets/inherited_model.dart index 2eda456d36f..688992546c2 100644 --- a/packages/flutter/lib/src/widgets/inherited_model.dart +++ b/packages/flutter/lib/src/widgets/inherited_model.dart @@ -6,53 +6,61 @@ import 'dart:collection'; import 'framework.dart'; -/// An [InheritedWidget] that's intended to be used as the base class for -/// models whose dependents may only depend on one part or "aspect" of the -/// overall model. +/// An [InheritedWidget] that's intended to be used as the base class for models +/// whose dependents may only depend on one part or "aspect" of the overall +/// model. /// /// An inherited widget's dependents are unconditionally rebuilt when the -/// inherited widget changes per [InheritedWidget.updateShouldNotify]. -/// This widget is similar except that dependents aren't rebuilt -/// unconditionally. +/// inherited widget changes per [InheritedWidget.updateShouldNotify]. This +/// widget is similar except that dependents aren't rebuilt unconditionally. /// -/// Widgets that depend on an [InheritedModel] qualify their dependence -/// with a value that indicates what "aspect" of the model they depend -/// on. When the model is rebuilt, dependents will also be rebuilt, but -/// only if there was a change in the model that corresponds to the aspect -/// they provided. +/// Widgets that depend on an [InheritedModel] qualify their dependence with a +/// value that indicates what "aspect" of the model they depend on. When the +/// model is rebuilt, dependents will also be rebuilt, but only if there was a +/// change in the model that corresponds to the aspect they provided. /// /// The type parameter `T` is the type of the model aspect objects. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=ml5uefGgkaA} /// /// Widgets create a dependency on an [InheritedModel] with a static method: -/// [InheritedModel.inheritFrom]. This method's `context` parameter -/// defines the subtree that will be rebuilt when the model changes. -/// Typically the `inheritFrom` method is called from a model-specific -/// static `of` method. For example: +/// [InheritedModel.inheritFrom]. This method's `context` parameter defines the +/// subtree that will be rebuilt when the model changes. Typically the +/// `inheritFrom` method is called from a model-specific static `maybeOf` or +/// `of` methods, a convention that is present in many Flutter framework classes +/// which look things up. For example: /// /// ```dart /// class MyModel extends InheritedModel { /// const MyModel({super.key, required super.child}); +/// /// // ... -/// static MyModel? of(BuildContext context, String aspect) { +/// static MyModel? maybeOf(BuildContext context, [String? aspect]) { /// return InheritedModel.inheritFrom(context, aspect: aspect); /// } +/// +/// // ... +/// static MyModel of(BuildContext context, [String? aspect]) { +/// final MyModel? result = maybeOf(context, aspect); +/// assert(result != null, 'Unable to find an instance of MyModel...'); +/// return result!; +/// } /// } /// ``` /// -/// Calling `MyModel.of(context, 'foo')` means that `context` should only -/// be rebuilt when the `foo` aspect of `MyModel` changes. If the aspect -/// is null, then the model supports all aspects. +/// Calling `MyModel.of(context, 'foo')` or `MyModel.maybeOf(context, +/// 'foo')` means that `context` should only be rebuilt when the `foo` aspect of +/// `MyModel` changes. If the `aspect` is null, then the model supports all +/// aspects. /// /// {@tool snippet} /// When the inherited model is rebuilt the [updateShouldNotify] and -/// [updateShouldNotifyDependent] methods are used to decide what -/// should be rebuilt. If [updateShouldNotify] returns true, then the -/// inherited model's [updateShouldNotifyDependent] method is tested for -/// each dependent and the set of aspect objects it depends on. -/// The [updateShouldNotifyDependent] method must compare the set of aspect -/// dependencies with the changes in the model itself. For example: +/// [updateShouldNotifyDependent] methods are used to decide what should be +/// rebuilt. If [updateShouldNotify] returns true, then the inherited model's +/// [updateShouldNotifyDependent] method is tested for each dependent and the +/// set of aspect objects it depends on. The [updateShouldNotifyDependent] +/// method must compare the set of aspect dependencies with the changes in the +/// model itself. For example: /// /// ```dart /// class ABModel extends InheritedModel { @@ -85,26 +93,26 @@ import 'framework.dart'; /// In the previous example the dependencies checked by /// [updateShouldNotifyDependent] are just the aspect strings passed to /// `dependOnInheritedWidgetOfExactType`. They're represented as a [Set] because -/// one Widget can depend on more than one aspect of the model. -/// If a widget depends on the model but doesn't specify an aspect, -/// then changes in the model will cause the widget to be rebuilt -/// unconditionally. +/// one Widget can depend on more than one aspect of the model. If a widget +/// depends on the model but doesn't specify an aspect, then changes in the +/// model will cause the widget to be rebuilt unconditionally. /// /// {@tool dartpad} -/// This example shows how to implement [InheritedModel] to rebuild a -/// widget based on a qualified dependence. When tapped on the "Resize Logo" button -/// only the logo widget is rebuilt while the background widget remains unaffected. +/// This example shows how to implement [InheritedModel] to rebuild a widget +/// based on a qualified dependence. When tapped on the "Resize Logo" button +/// only the logo widget is rebuilt while the background widget remains +/// unaffected. /// /// ** See code in examples/api/lib/widgets/inherited_model/inherited_model.0.dart ** /// {@end-tool} /// /// See also: /// -/// * [InheritedWidget], an inherited widget that only notifies dependents -/// when its value is different. -/// * [InheritedNotifier], an inherited widget whose value can be a -/// [Listenable], and which will notify dependents whenever the value -/// sends notifications. +/// * [InheritedWidget], an inherited widget that only notifies dependents when +/// its value is different. +/// * [InheritedNotifier], an inherited widget whose value can be a +/// [Listenable], and which will notify dependents whenever the value sends +/// notifications. abstract class InheritedModel extends InheritedWidget { /// Creates an inherited widget that supports dependencies qualified by /// "aspects", i.e. a descendant widget can indicate that it should diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 65c62d6cb66..7c13750892e 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -689,12 +689,53 @@ class HeroControllerScope extends InheritedWidget { final HeroController? controller; /// Retrieves the [HeroController] from the closest [HeroControllerScope] - /// ancestor. - static HeroController? of(BuildContext context) { + /// ancestor, or null if none exists. + /// + /// Calling this method will create a dependency on the closest + /// [HeroControllerScope] in the [context], if there is one. + /// + /// See also: + /// + /// * [HeroControllerScope.of], which is similar to this method, but asserts + /// if no [HeroControllerScope] ancestor is found. + static HeroController? maybeOf(BuildContext context) { final HeroControllerScope? host = context.dependOnInheritedWidgetOfExactType(); return host?.controller; } + /// Retrieves the [HeroController] from the closest [HeroControllerScope] + /// ancestor. + /// + /// If no ancestor is found, this method will assert in debug mode, and throw + /// an exception in release mode. + /// + /// Calling this method will create a dependency on the closest + /// [HeroControllerScope] in the [context]. + /// + /// See also: + /// + /// * [HeroControllerScope.maybeOf], which is similar to this method, but + /// returns null if no [HeroControllerScope] ancestor is found. + static HeroController of(BuildContext context) { + final HeroController? controller = maybeOf(context); + assert(() { + if (controller == null) { + throw FlutterError( + 'HeroControllerScope.of() was called with a context that does not contain a ' + 'HeroControllerScope widget.\n' + 'No HeroControllerScope widget ancestor could be found starting from the ' + 'context that was passed to HeroControllerScope.of(). This can happen ' + 'because you are using a widget that looks for a HeroControllerScope ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return controller!; + } + @override bool updateShouldNotify(HeroControllerScope oldWidget) { return oldWidget.controller != controller; @@ -3350,7 +3391,7 @@ class NavigatorState extends State with TickerProviderStateMixin, Res @override void didChangeDependencies() { super.didChangeDependencies(); - _updateHeroController(HeroControllerScope.of(context)); + _updateHeroController(HeroControllerScope.maybeOf(context)); for (final _RouteEntry entry in _history) { entry.route.changedExternalState(); } diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 93f302980d7..83b871b436a 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -1088,7 +1088,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont void updateParent() { _outerPosition?.setParent( - _parent ?? PrimaryScrollController.of(_state.context), + _parent ?? PrimaryScrollController.maybeOf(_state.context), ); } diff --git a/packages/flutter/lib/src/widgets/page_storage.dart b/packages/flutter/lib/src/widgets/page_storage.dart index 21ead5eafd1..2360e44664f 100644 --- a/packages/flutter/lib/src/widgets/page_storage.dart +++ b/packages/flutter/lib/src/widgets/page_storage.dart @@ -174,22 +174,66 @@ class PageStorage extends StatelessWidget { /// The page storage bucket to use for this subtree. final PageStorageBucket bucket; - /// The bucket from the closest instance of this class that encloses the given context. + /// The [PageStorageBucket] from the closest instance of a [PageStorage] + /// widget that encloses the given context. /// /// Returns null if none exists. /// /// Typical usage is as follows: /// /// ```dart - /// PageStorageBucket bucket = PageStorage.of(context)!; + /// PageStorageBucket? bucket = PageStorage.of(context); /// ``` /// /// This method can be expensive (it walks the element tree). - static PageStorageBucket? of(BuildContext context) { + /// + /// See also: + /// + /// * [PageStorage.of], which is similar to this method, but + /// asserts if no [PageStorage] ancestor is found. + static PageStorageBucket? maybeOf(BuildContext context) { final PageStorage? widget = context.findAncestorWidgetOfExactType(); return widget?.bucket; } + /// The [PageStorageBucket] from the closest instance of a [PageStorage] + /// widget that encloses the given context. + /// + /// If no ancestor is found, this method will assert in debug mode, and throw + /// an exception in release mode. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// PageStorageBucket bucket = PageStorage.of(context); + /// ``` + /// + /// This method can be expensive (it walks the element tree). + /// + /// See also: + /// + /// * [PageStorage.maybeOf], which is similar to this method, but + /// returns null if no [PageStorage] ancestor is found. + static PageStorageBucket of(BuildContext context) { + final PageStorageBucket? bucket = maybeOf(context); + assert(() { + if (bucket == null) { + throw FlutterError( + 'PageStorage.of() was called with a context that does not contain a ' + 'PageStorage widget.\n' + 'No PageStorage widget ancestor could be found starting from the ' + 'context that was passed to PageStorage.of(). This can happen ' + 'because you are using a widget that looks for a PageStorage ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return bucket!; + } + @override Widget build(BuildContext context) => child; } diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 5b87fade1b3..ee96f5f9840 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -403,13 +403,13 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri @override void saveScrollOffset() { - PageStorage.of(context.storageContext)?.writeState(context.storageContext, _cachedPage ?? getPageFromPixels(pixels, viewportDimension)); + PageStorage.maybeOf(context.storageContext)?.writeState(context.storageContext, _cachedPage ?? getPageFromPixels(pixels, viewportDimension)); } @override void restoreScrollOffset() { if (!hasPixels) { - final double? value = PageStorage.of(context.storageContext)?.readState(context.storageContext) as double?; + final double? value = PageStorage.maybeOf(context.storageContext)?.readState(context.storageContext) as double?; if (value != null) { _pageToUseOnStartup = value; } diff --git a/packages/flutter/lib/src/widgets/primary_scroll_controller.dart b/packages/flutter/lib/src/widgets/primary_scroll_controller.dart index b1bdf323e69..5e292b0e0a5 100644 --- a/packages/flutter/lib/src/widgets/primary_scroll_controller.dart +++ b/packages/flutter/lib/src/widgets/primary_scroll_controller.dart @@ -125,11 +125,52 @@ class PrimaryScrollController extends InheritedWidget { /// /// Returns null if there is no [ScrollController] associated with the given /// context. - static ScrollController? of(BuildContext context) { + /// + /// Calling this method will create a dependency on the closest + /// [PrimaryScrollController] in the [context], if there is one. + /// + /// See also: + /// + /// * [PrimaryScrollController.maybeOf], which is similar to this method, but + /// asserts if no [PrimaryScrollController] ancestor is found. + static ScrollController? maybeOf(BuildContext context) { final PrimaryScrollController? result = context.dependOnInheritedWidgetOfExactType(); return result?.controller; } + /// Returns the [ScrollController] most closely associated with the given + /// context. + /// + /// If no ancestor is found, this method will assert in debug mode, and throw + /// an exception in release mode. + /// + /// Calling this method will create a dependency on the closest + /// [PrimaryScrollController] in the [context]. + /// + /// See also: + /// + /// * [PrimaryScrollController.maybeOf], which is similar to this method, but + /// returns null if no [PrimaryScrollController] ancestor is found. + static ScrollController of(BuildContext context) { + final ScrollController? controller = maybeOf(context); + assert(() { + if (controller == null) { + throw FlutterError( + 'PrimaryScrollController.of() was called with a context that does not contain a ' + 'PrimaryScrollController widget.\n' + 'No PrimaryScrollController widget ancestor could be found starting from the ' + 'context that was passed to PrimaryScrollController.of(). This can happen ' + 'because you are using a widget that looks for a PrimaryScrollController ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return controller!; + } + @override bool updateShouldNotify(PrimaryScrollController oldWidget) => controller != oldWidget.controller; diff --git a/packages/flutter/lib/src/widgets/reorderable_list.dart b/packages/flutter/lib/src/widgets/reorderable_list.dart index 60ff7f3dc90..6521aed13e6 100644 --- a/packages/flutter/lib/src/widgets/reorderable_list.dart +++ b/packages/flutter/lib/src/widgets/reorderable_list.dart @@ -609,7 +609,7 @@ class SliverReorderableListState extends State with Ticke @override void didChangeDependencies() { super.didChangeDependencies(); - _scrollable = Scrollable.of(context)!; + _scrollable = Scrollable.of(context); if (_autoScroller?.scrollable != _scrollable) { _autoScroller?.stopAutoScroll(); _autoScroller = EdgeDraggingAutoScroller( diff --git a/packages/flutter/lib/src/widgets/restoration.dart b/packages/flutter/lib/src/widgets/restoration.dart index 24a195ebe28..617dd787447 100644 --- a/packages/flutter/lib/src/widgets/restoration.dart +++ b/packages/flutter/lib/src/widgets/restoration.dart @@ -65,18 +65,63 @@ class RestorationScope extends StatefulWidget { /// Returns the [RestorationBucket] inserted into the widget tree by the /// closest ancestor [RestorationScope] of `context`. /// + /// {@template flutter.widgets.restoration.RestorationScope.bucket_warning} /// To avoid accidentally overwriting data already stored in the bucket by its /// owner, data should not be stored directly in the bucket returned by this /// method. Instead, consider claiming a child bucket from the returned bucket /// (via [RestorationBucket.claimChild]) and store the restoration data in /// that child. + /// {@endtemplate} /// /// This method returns null if state restoration is turned off for this /// subtree. - static RestorationBucket? of(BuildContext context) { + /// + /// Calling this method will create a dependency on the closest + /// [RestorationScope] in the [context], if there is one. + /// + /// See also: + /// + /// * [RestorationScope.maybeOf], which is similar to this method, but asserts + /// if no [RestorationScope] ancestor is found. + static RestorationBucket? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType()?.bucket; } + /// Returns the [RestorationBucket] inserted into the widget tree by the + /// closest ancestor [RestorationScope] of `context`. + /// + /// {@macro flutter.widgets.restoration.RestorationScope.bucket_warning} + /// + /// This method will assert in debug mode and throw an exception in release + /// mode if state restoration is turned off for this subtree. + /// + /// Calling this method will create a dependency on the closest + /// [RestorationScope] in the [context]. + /// + /// See also: + /// + /// * [RestorationScope.maybeOf], which is similar to this method, but returns + /// null if no [RestorationScope] ancestor is found. + static RestorationBucket of(BuildContext context) { + final RestorationBucket? bucket = maybeOf(context); + assert(() { + if (bucket == null) { + throw FlutterError( + 'RestorationScope.of() was called with a context that does not contain a ' + 'RestorationScope widget.\n' + 'No RestorationScope widget ancestor could be found starting from the ' + 'context that was passed to RestorationScope.of(). This can happen ' + 'because you are using a widget that looks for a RestorationScope ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return bucket!; + } + /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.ProxyWidget.child} @@ -250,7 +295,7 @@ class _RootRestorationScopeState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - _ancestorBucket = RestorationScope.of(context); + _ancestorBucket = RestorationScope.maybeOf(context); _loadRootBucketIfNecessary(); _okToRenderBlankContainer ??= widget.restorationId != null && _needsRootBucketInserted; } @@ -834,7 +879,7 @@ mixin RestorationMixin on State { if (restorationId == null) { return false; } - final RestorationBucket? potentialNewParent = RestorationScope.of(context); + final RestorationBucket? potentialNewParent = RestorationScope.maybeOf(context); return potentialNewParent != _currentParent && (potentialNewParent?.isReplacing ?? false); } @@ -850,7 +895,7 @@ mixin RestorationMixin on State { final RestorationBucket? oldBucket = _bucket; final bool needsRestore = restorePending; - _currentParent = RestorationScope.of(context); + _currentParent = RestorationScope.maybeOf(context); final bool didReplaceBucket = _updateBucketIfNecessary(parent: _currentParent, restorePending: needsRestore); diff --git a/packages/flutter/lib/src/widgets/scroll_notification_observer.dart b/packages/flutter/lib/src/widgets/scroll_notification_observer.dart index 8fae00e9b57..aa329ec14f7 100644 --- a/packages/flutter/lib/src/widgets/scroll_notification_observer.dart +++ b/packages/flutter/lib/src/widgets/scroll_notification_observer.dart @@ -45,13 +45,13 @@ class _ListenerEntry extends LinkedListEntry<_ListenerEntry> { /// To add a listener to a [ScrollNotificationObserver] ancestor: /// /// ```dart -/// ScrollNotificationObserver.of(context)!.addListener(_listener); +/// ScrollNotificationObserver.of(context).addListener(_listener); /// ``` /// /// To remove the listener from a [ScrollNotificationObserver] ancestor: /// /// ```dart -/// ScrollNotificationObserver.of(context)!.removeListener(_listener); +/// ScrollNotificationObserver.of(context).removeListener(_listener); /// ``` /// /// Stateful widgets that share an ancestor [ScrollNotificationObserver] typically @@ -85,11 +85,52 @@ class ScrollNotificationObserver extends StatefulWidget { /// The closest instance of this class that encloses the given context. /// - /// If there is no enclosing [ScrollNotificationObserver] widget, then null is returned. - static ScrollNotificationObserverState? of(BuildContext context) { + /// If there is no enclosing [ScrollNotificationObserver] widget, then null is + /// returned. + /// + /// Calling this method will create a dependency on the closest + /// [ScrollNotificationObserver] in the [context], if there is one. + /// + /// See also: + /// + /// * [ScrollNotificationObserver.of], which is similar to this method, but + /// asserts if no [ScrollNotificationObserver] ancestor is found. + static ScrollNotificationObserverState? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<_ScrollNotificationObserverScope>()?._scrollNotificationObserverState; } + /// The closest instance of this class that encloses the given context. + /// + /// If no ancestor is found, this method will assert in debug mode, and throw + /// an exception in release mode. + /// + /// Calling this method will create a dependency on the closest + /// [ScrollNotificationObserver] in the [context]. + /// + /// See also: + /// + /// * [ScrollNotificationObserver.maybeOf], which is similar to this method, + /// but returns null if no [ScrollNotificationObserver] ancestor is found. + static ScrollNotificationObserverState of(BuildContext context) { + final ScrollNotificationObserverState? observerState = maybeOf(context); + assert(() { + if (observerState == null) { + throw FlutterError( + 'ScrollNotificationObserver.of() was called with a context that does not contain a ' + 'ScrollNotificationObserver widget.\n' + 'No ScrollNotificationObserver widget ancestor could be found starting from the ' + 'context that was passed to ScrollNotificationObserver.of(). This can happen ' + 'because you are using a widget that looks for a ScrollNotificationObserver ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return observerState!; + } + @override ScrollNotificationObserverState createState() => ScrollNotificationObserverState(); } diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index 34e12dff6fe..db363517c37 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -402,7 +402,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { // TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage. @protected void saveScrollOffset() { - PageStorage.of(context.storageContext)?.writeState(context.storageContext, pixels); + PageStorage.maybeOf(context.storageContext)?.writeState(context.storageContext, pixels); } /// Called whenever the [ScrollPosition] is created, to restore the scroll @@ -424,7 +424,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { @protected void restoreScrollOffset() { if (!hasPixels) { - final double? value = PageStorage.of(context.storageContext)?.readState(context.storageContext) as double?; + final double? value = PageStorage.maybeOf(context.storageContext)?.readState(context.storageContext) as double?; if (value != null) { correctPixels(value); } @@ -703,7 +703,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { }) { assert(alignmentPolicy != null); assert(object.attached); - final RenderAbstractViewport viewport = RenderAbstractViewport.of(object)!; + final RenderAbstractViewport viewport = RenderAbstractViewport.of(object); assert(viewport != null); Rect? targetRect; diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index 0a7f4c9a447..d3f74745f68 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -411,7 +411,7 @@ abstract class ScrollView extends StatelessWidget { ?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection); final ScrollController? scrollController = effectivePrimary - ? PrimaryScrollController.of(context) + ? PrimaryScrollController.maybeOf(context) : controller; final Scrollable scrollable = Scrollable( diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 0aee4f1affa..4daffa9bfdc 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -289,21 +289,66 @@ class Scrollable extends StatefulWidget { properties.add(StringProperty('restorationId', restorationId)); } - /// The state from the closest instance of this class that encloses the given context. + /// The state from the closest instance of this class that encloses the given + /// context, or null if none is found. /// /// Typical usage is as follows: /// /// ```dart - /// ScrollableState scrollable = Scrollable.of(context)!; + /// ScrollableState? scrollable = Scrollable.maybeOf(context); /// ``` /// /// Calling this method will create a dependency on the closest [Scrollable] /// in the [context], if there is one. - static ScrollableState? of(BuildContext context) { + /// + /// See also: + /// + /// * [Scrollable.of], which is similar to this method, but asserts + /// if no [Scrollable] ancestor is found. + static ScrollableState? maybeOf(BuildContext context) { final _ScrollableScope? widget = context.dependOnInheritedWidgetOfExactType<_ScrollableScope>(); return widget?.scrollable; } + /// The state from the closest instance of this class that encloses the given + /// context. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// ScrollableState scrollable = Scrollable.of(context); + /// ``` + /// + /// Calling this method will create a dependency on the closest [Scrollable] + /// in the [context]. + /// + /// If no [Scrollable] ancestor is found, then this method will assert in + /// debug mode, and throw an exception in release mode. + /// + /// See also: + /// + /// * [Scrollable.maybeOf], which is similar to this method, but returns null + /// if no [Scrollable] ancestor is found. + static ScrollableState of(BuildContext context) { + final ScrollableState? scrollableState = maybeOf(context); + assert(() { + if (scrollableState == null) { + throw FlutterError( + 'Scrollable.of() was called with a context that does not contain a ' + 'Scrollable widget.\n' + 'No Scrollable widget ancestor could be found starting from the ' + 'context that was passed to Scrollable.of(). This can happen ' + 'because you are using a widget that looks for a Scrollable ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return scrollableState!; + } + /// Provides a heuristic to determine if expensive frame-bound tasks should be /// deferred for the [context] at a specific point in time. /// @@ -343,7 +388,7 @@ class Scrollable extends StatefulWidget { // the `targetRenderObject` invisible. // Also see https://github.com/flutter/flutter/issues/65100 RenderObject? targetRenderObject; - ScrollableState? scrollable = Scrollable.of(context); + ScrollableState? scrollable = Scrollable.maybeOf(context); while (scrollable != null) { futures.add(scrollable.position.ensureVisible( context.findRenderObject()!, @@ -356,7 +401,7 @@ class Scrollable extends StatefulWidget { targetRenderObject = targetRenderObject ?? context.findRenderObject(); context = scrollable.context; - scrollable = Scrollable.of(context); + scrollable = Scrollable.maybeOf(context); } if (futures.isEmpty || duration == Duration.zero) { @@ -1618,11 +1663,11 @@ class ScrollAction extends Action { final bool contextIsValid = focus != null && focus.context != null; if (contextIsValid) { // Check for primary scrollable within the current context - if (Scrollable.of(focus.context!) != null) { + if (Scrollable.maybeOf(focus.context!) != null) { return true; } // Check for fallback scrollable with context from PrimaryScrollController - final ScrollController? primaryScrollController = PrimaryScrollController.of(focus.context!); + final ScrollController? primaryScrollController = PrimaryScrollController.maybeOf(focus.context!); return primaryScrollController != null && primaryScrollController.hasClients; } return false; @@ -1709,11 +1754,11 @@ class ScrollAction extends Action { @override void invoke(ScrollIntent intent) { - ScrollableState? state = Scrollable.of(primaryFocus!.context!); + ScrollableState? state = Scrollable.maybeOf(primaryFocus!.context!); if (state == null) { - final ScrollController? primaryScrollController = PrimaryScrollController.of(primaryFocus!.context!); + final ScrollController primaryScrollController = PrimaryScrollController.of(primaryFocus!.context!); assert (() { - if (primaryScrollController!.positions.length != 1) { + if (primaryScrollController.positions.length != 1) { throw FlutterError.fromParts([ ErrorSummary( 'A ScrollAction was invoked with the PrimaryScrollController, but ' @@ -1735,11 +1780,11 @@ class ScrollAction extends Action { return true; }()); - if (primaryScrollController!.position.context.notificationContext == null - && Scrollable.of(primaryScrollController.position.context.notificationContext!) == null) { + if (primaryScrollController.position.context.notificationContext == null + && Scrollable.maybeOf(primaryScrollController.position.context.notificationContext!) == null) { return; } - state = Scrollable.of(primaryScrollController.position.context.notificationContext!); + state = Scrollable.maybeOf(primaryScrollController.position.context.notificationContext!); } assert(state != null, '$ScrollAction was invoked on a context that has no scrollable parent'); assert(state!.position.hasPixels, 'Scrollable must be laid out before it can be scrolled via a ScrollAction'); diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart index 630d72636c7..238bd0bfbf6 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -1550,7 +1550,7 @@ class RawScrollbarState extends State with TickerProv } void _validateInteractions(AnimationStatus status) { - final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.of(context); + final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.maybeOf(context); if (status == AnimationStatus.dismissed) { assert(_fadeoutOpacityAnimation.value == 0.0); // We do not check for a valid scroll position if the scrollbar is not @@ -1566,7 +1566,7 @@ class RawScrollbarState extends State with TickerProv if (!mounted) { return true; } - final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.of(context); + final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.maybeOf(context); final bool tryPrimary = widget.controller == null; final String controllerForError = tryPrimary ? 'PrimaryScrollController' @@ -1788,7 +1788,7 @@ class RawScrollbarState extends State with TickerProv @mustCallSuper void handleThumbPressStart(Offset localPosition) { assert(_debugCheckHasValidScrollPosition()); - _currentController = widget.controller ?? PrimaryScrollController.of(context); + _currentController = widget.controller ?? PrimaryScrollController.maybeOf(context); final Axis? direction = getScrollbarDirection(); if (direction == null) { return; @@ -1835,7 +1835,7 @@ class RawScrollbarState extends State with TickerProv void _handleTrackTapDown(TapDownDetails details) { // The Scrollbar should page towards the position of the tap on the track. assert(_debugCheckHasValidScrollPosition()); - _currentController = widget.controller ?? PrimaryScrollController.of(context); + _currentController = widget.controller ?? PrimaryScrollController.maybeOf(context); final ScrollPosition position = _currentController!.position; if (!position.physics.shouldAcceptUserOffset(position)) { @@ -1844,7 +1844,7 @@ class RawScrollbarState extends State with TickerProv double scrollIncrement; // Is an increment calculator available? - final ScrollIncrementCalculator? calculator = Scrollable.of( + final ScrollIncrementCalculator? calculator = Scrollable.maybeOf( _currentController!.position.context.notificationContext!, )?.widget.incrementCalculator; if (calculator != null) { @@ -1893,7 +1893,7 @@ class RawScrollbarState extends State with TickerProv // ScrollController takes precedence over ScrollNotification bool _shouldUpdatePainter(Axis notificationAxis) { final ScrollController? scrollController = widget.controller ?? - PrimaryScrollController.of(context); + PrimaryScrollController.maybeOf(context); // Only update the painter of this scrollbar if the notification // metrics do not conflict with the information we have from the scroll // controller. @@ -1979,7 +1979,7 @@ class RawScrollbarState extends State with TickerProv Map get _gestures { final Map gestures = {}; - final ScrollController? controller = widget.controller ?? PrimaryScrollController.of(context); + final ScrollController? controller = widget.controller ?? PrimaryScrollController.maybeOf(context); if (controller == null || !enableGestures) { return gestures; } diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart index 3ae6361548a..d2361d723ab 100644 --- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart @@ -241,7 +241,7 @@ class SingleChildScrollView extends StatelessWidget { ?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection); final ScrollController? scrollController = effectivePrimary - ? PrimaryScrollController.of(context) + ? PrimaryScrollController.maybeOf(context) : controller; Widget scrollable = Scrollable( diff --git a/packages/flutter/lib/src/widgets/sliver_persistent_header.dart b/packages/flutter/lib/src/widgets/sliver_persistent_header.dart index f548e9bf114..ca394cd12c4 100644 --- a/packages/flutter/lib/src/widgets/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/widgets/sliver_persistent_header.dart @@ -212,7 +212,7 @@ class _FloatingHeaderState extends State<_FloatingHeader> { if (_position != null) { _position!.isScrollingNotifier.removeListener(_isScrollingListener); } - _position = Scrollable.of(context)?.position; + _position = Scrollable.maybeOf(context)?.position; if (_position != null) { _position!.isScrollingNotifier.addListener(_isScrollingListener); } diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index 4ce409596ec..622f1a50e11 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -245,19 +245,67 @@ class DefaultTextHeightBehavior extends InheritedTheme { /// {@macro dart.ui.textHeightBehavior} final TextHeightBehavior textHeightBehavior; - /// The closest instance of this class that encloses the given context. + /// The closest instance of [DefaultTextHeightBehavior] that encloses the + /// given context, or null if none is found. /// /// If no such instance exists, this method will return `null`. /// + /// Calling this method will create a dependency on the closest + /// [DefaultTextHeightBehavior] in the [context], if there is one. + /// /// Typical usage is as follows: /// /// ```dart - /// TextHeightBehavior defaultTextHeightBehavior = DefaultTextHeightBehavior.of(context)!; + /// TextHeightBehavior? defaultTextHeightBehavior = DefaultTextHeightBehavior.of(context); /// ``` - static TextHeightBehavior? of(BuildContext context) { + /// + /// See also: + /// + /// * [DefaultTextHeightBehavior.maybeOf], which is similar to this method, + /// but asserts if no [DefaultTextHeightBehavior] ancestor is found. + static TextHeightBehavior? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType()?.textHeightBehavior; } + /// The closest instance of [DefaultTextHeightBehavior] that encloses the + /// given context. + /// + /// If no such instance exists, this method will assert in debug mode, and + /// throw an exception in release mode. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// TextHeightBehavior defaultTextHeightBehavior = DefaultTextHeightBehavior.of(context); + /// ``` + /// + /// Calling this method will create a dependency on the closest + /// [DefaultTextHeightBehavior] in the [context]. + /// + /// See also: + /// + /// * [DefaultTextHeightBehavior.maybeOf], which is similar to this method, + /// but returns null if no [DefaultTextHeightBehavior] ancestor is found. + static TextHeightBehavior of(BuildContext context) { + final TextHeightBehavior? behavior = maybeOf(context); + assert(() { + if (behavior == null) { + throw FlutterError( + 'DefaultTextHeightBehavior.of() was called with a context that does not contain a ' + 'DefaultTextHeightBehavior widget.\n' + 'No DefaultTextHeightBehavior widget ancestor could be found starting from the ' + 'context that was passed to DefaultTextHeightBehavior.of(). This can happen ' + 'because you are using a widget that looks for a DefaultTextHeightBehavior ' + 'ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ); + } + return true; + }()); + return behavior!; + } + @override bool updateShouldNotify(DefaultTextHeightBehavior oldWidget) { return textHeightBehavior != oldWidget.textHeightBehavior; @@ -564,7 +612,7 @@ class Text extends StatelessWidget { maxLines: maxLines ?? defaultTextStyle.maxLines, strutStyle: strutStyle, textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis, - textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context), + textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context), selectionRegistrar: registrar, selectionColor: selectionColor ?? DefaultSelectionStyle.of(context).selectionColor ?? DefaultSelectionStyle.defaultColor, text: TextSpan( diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 959d6b8cc42..e50cde618be 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -1893,7 +1893,7 @@ class TextSelectionGestureDetectorBuilder { final ScrollableState? scrollableState = delegate.editableTextKey.currentContext == null ? null - : Scrollable.of(delegate.editableTextKey.currentContext!); + : Scrollable.maybeOf(delegate.editableTextKey.currentContext!); return scrollableState == null ? 0.0 : scrollableState.position.pixels; diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index bd0f80677cc..14c3dd2fafd 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -51,7 +51,7 @@ Widget buildSliverAppBarApp({ } ScrollController primaryScrollController(WidgetTester tester) { - return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)))!; + return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView))); } TextStyle? iconStyle(WidgetTester tester, IconData icon) { diff --git a/packages/flutter/test/material/data_table_test.dart b/packages/flutter/test/material/data_table_test.dart index d05ddc092d9..ad4733e6f0e 100644 --- a/packages/flutter/test/material/data_table_test.dart +++ b/packages/flutter/test/material/data_table_test.dart @@ -1589,7 +1589,7 @@ void main() { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Content1'))); await tester.pump(const Duration(milliseconds: 200)); // splash is well underway - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell)))as RenderBox; expect(box, paints..circle(x: 68.0, y: 24.0, color: pressedColor)); await gesture.up(); }); diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index 3089c70370b..43a7c8e8610 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -1011,7 +1011,7 @@ void main() { await tester.tap(find.byKey(buttonKey)); await tester.pump(); - final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView)))!; + final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView))); // Make sure there is no overscroll expect(scrollController.offset, scrollController.position.maxScrollExtent); @@ -1854,7 +1854,7 @@ void main() { double getMenuScroll() { double scrollPosition; - final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView)))!; + final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView))); assert(scrollController != null); scrollPosition = scrollController.position.pixels; assert(scrollPosition != null); @@ -1890,7 +1890,7 @@ void main() { double getMenuScroll() { double scrollPosition; - final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView)))!; + final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView))); assert(scrollController != null); scrollPosition = scrollController.position.pixels; assert(scrollPosition != null); @@ -1927,7 +1927,7 @@ void main() { double getMenuScroll() { double scrollPosition; - final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView)))!; + final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView))); assert(scrollController != null); scrollPosition = scrollController.position.pixels; assert(scrollPosition != null); @@ -1964,7 +1964,7 @@ void main() { double getMenuScroll() { double scrollPosition; - final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView)))!; + final ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView))); assert(scrollController != null); scrollPosition = scrollController.position.pixels; assert(scrollPosition != null); @@ -3066,7 +3066,7 @@ void main() { await tester.tap(find.text('0')); await tester.pumpAndSettle(); - ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView)))!; + ScrollController scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView))); // The scrollbar shouldn't show if the list fits into the screen. expect(scrollController.position.maxScrollExtent, 0); expect(find.byType(Scrollbar), isNot(paints..rect())); @@ -3082,7 +3082,7 @@ void main() { await tester.tap(find.text('0')); await tester.pumpAndSettle(); - scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView)))!; + scrollController = PrimaryScrollController.of(tester.element(find.byType(ListView))); // The scrollbar is shown when the list is longer than the height of the screen. expect(scrollController.position.maxScrollExtent > 0, isTrue); expect(find.byType(Scrollbar), paints..rect()); diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index 3ce051537bc..bb0d15f0d1f 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -1279,7 +1279,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: NoSplash.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 0)); await gesture.up(); @@ -1290,7 +1290,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: InkRipple.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 1)); await gesture.up(); diff --git a/packages/flutter/test/material/filled_button_test.dart b/packages/flutter/test/material/filled_button_test.dart index fa659d30cf0..fefdb72d64f 100644 --- a/packages/flutter/test/material/filled_button_test.dart +++ b/packages/flutter/test/material/filled_button_test.dart @@ -1307,7 +1307,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: NoSplash.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 0)); await gesture.up(); @@ -1318,7 +1318,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: InkRipple.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 1)); await gesture.up(); diff --git a/packages/flutter/test/material/ink_paint_test.dart b/packages/flutter/test/material/ink_paint_test.dart index 8a52797f923..a574fcead68 100644 --- a/packages/flutter/test/material/ink_paint_test.dart +++ b/packages/flutter/test/material/ink_paint_test.dart @@ -75,7 +75,7 @@ void main() { await tester.pump(); // start gesture await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as RenderBox; expect( box, paints @@ -127,7 +127,7 @@ void main() { await tester.tapAt(tapDownOffset); await tester.pump(); // start gesture - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell)))as RenderBox; bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0; bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0; @@ -205,7 +205,7 @@ void main() { await tester.pump(); // start gesture await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell)))as RenderBox; expect( box, paints @@ -337,7 +337,7 @@ void main() { await tester.sendKeyEvent(LogicalKeyboardKey.space); await tester.pump(); - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell)))as RenderBox; // ripplePattern always add a translation of topLeft. expect(box, ripplePattern(30.0, 0)); @@ -433,7 +433,7 @@ void main() { await gesture.moveTo(Offset.zero); await gesture.up(); // generates a tap cancel - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell)))as RenderBox; expect(box, paints..everything((Symbol method, List arguments) { if (method != #drawCircle) { return true; diff --git a/packages/flutter/test/material/ink_sparkle_test.dart b/packages/flutter/test/material/ink_sparkle_test.dart index f55ea2cc233..0a780b028be 100644 --- a/packages/flutter/test/material/ink_sparkle_test.dart +++ b/packages/flutter/test/material/ink_sparkle_test.dart @@ -50,7 +50,7 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 200)); - final MaterialInkController material = Material.of(tester.element(buttonFinder))!; + final MaterialInkController material = Material.of(tester.element(buttonFinder)); expect(material, paintsExactlyCountTimes(#drawRect, 1)); // ignore: avoid_dynamic_calls @@ -81,7 +81,7 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 200)); - final MaterialInkController material = Material.of(tester.element(buttonFinder))!; + final MaterialInkController material = Material.of(tester.element(buttonFinder)); expect(material, paintsExactlyCountTimes(#drawPaint, 1)); }, skip: kIsWeb, // [intended] shaders are not yet supported for web. diff --git a/packages/flutter/test/material/ink_splash_test.dart b/packages/flutter/test/material/ink_splash_test.dart index 0537aae7ed2..c0886c5d51c 100644 --- a/packages/flutter/test/material/ink_splash_test.dart +++ b/packages/flutter/test/material/ink_splash_test.dart @@ -48,7 +48,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: NoSplash.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 0)); await gesture.up(); @@ -59,7 +59,7 @@ void main() { await tester.pumpWidget(buildFrame()); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 1)); await gesture.up(); diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart index 9f3cd2ba23b..8168aa728a9 100644 --- a/packages/flutter/test/material/ink_well_test.dart +++ b/packages/flutter/test/material/ink_well_test.dart @@ -865,7 +865,7 @@ void main() { ), ), ); - final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey)))!; + final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey))); // Press final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(innerKey)), pointer: 1); @@ -938,7 +938,7 @@ void main() { ), ), ); - final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey)))!; + final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey))); // Press final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(innerKey)), pointer: 1); @@ -1019,7 +1019,7 @@ void main() { ), ), ); - final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey)))!; + final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey))); // Press middle await tester.startGesture(tester.getTopLeft(find.byKey(middleKey)) + const Offset(1, 1), pointer: 1); @@ -1079,7 +1079,7 @@ void main() { ), ), ); - final MaterialInkController material = Material.of(tester.element(find.byKey(leftKey)))!; + final MaterialInkController material = Material.of(tester.element(find.byKey(leftKey))); final Offset parentPosition = tester.getTopLeft(find.byKey(parentKey)) + const Offset(1, 1); @@ -1191,7 +1191,7 @@ void main() { ), ), ); - final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey)))!; + final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey))); // Press inner final TestGesture gesture = await tester.startGesture(const Offset(100, 50), pointer: 1); @@ -1265,7 +1265,7 @@ void main() { ), ), ); - final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey)))!; + final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey))); // Press final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(innerKey)), pointer: 1); diff --git a/packages/flutter/test/material/material_test.dart b/packages/flutter/test/material/material_test.dart index 79d911e5247..ca4a6af5df1 100644 --- a/packages/flutter/test/material/material_test.dart +++ b/packages/flutter/test/material/material_test.dart @@ -1006,7 +1006,7 @@ void main() { child: SizedBox(key: sizedBoxKey, width: 20, height: 20), ), )); - final MaterialInkController controller = Material.of(sizedBoxKey.currentContext!)!; + final MaterialInkController controller = Material.of(sizedBoxKey.currentContext!); final TrackPaintInkFeature tracker = TrackPaintInkFeature( controller: controller, @@ -1015,7 +1015,7 @@ void main() { controller.addInkFeature(tracker); expect(tracker.paintCount, 0); - // Force a repaint. Since it's offstage, the ink feture should not get painted. + // Force a repaint. Since it's offstage, the ink feature should not get painted. materialKey.currentContext!.findRenderObject()!.paint(PaintingContext(ContainerLayer(), Rect.largest), Offset.zero); expect(tracker.paintCount, 0); diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index dcc53eebbfa..0bfb6e5cf53 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -1442,7 +1442,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: NoSplash.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 0)); await gesture.up(); @@ -1453,7 +1453,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: InkRipple.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 1)); await gesture.up(); diff --git a/packages/flutter/test/material/raw_material_button_test.dart b/packages/flutter/test/material/raw_material_button_test.dart index 9bacdd29380..18ab6605be7 100644 --- a/packages/flutter/test/material/raw_material_button_test.dart +++ b/packages/flutter/test/material/raw_material_button_test.dart @@ -32,7 +32,7 @@ void main() { await tester.tap(find.text('BUTTON')); await tester.pump(const Duration(milliseconds: 10)); - final RenderBox splash = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox splash = Material.of(tester.element(find.byType(InkWell))) as RenderBox; expect(splash, paints..circle(color: splashColor)); await tester.pumpAndSettle(); @@ -72,7 +72,7 @@ void main() { await tester.pump(const Duration(milliseconds: 10)); if (!kIsWeb) { - final RenderBox splash = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox splash = Material.of(tester.element(find.byType(InkWell))) as RenderBox; expect(splash, paints..circle(color: splashColor)); } @@ -90,7 +90,7 @@ void main() { await tester.sendKeyEvent(LogicalKeyboardKey.space); await tester.pump(const Duration(milliseconds: 10)); - final RenderBox splash = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox splash = Material.of(tester.element(find.byType(InkWell))) as RenderBox; expect(splash, paints..circle(color: splashColor)); await tester.pumpAndSettle(); @@ -195,7 +195,7 @@ void main() { await tester.pump(); // start gesture await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as RenderBox; // centered in material button. expect(box, paints..circle(x: 44.0, y: 18.0, color: splashColor)); await gesture.up(); @@ -226,7 +226,7 @@ void main() { final TestGesture gesture = await tester.startGesture(top); await tester.pump(); // start gesture await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as RenderBox; // paints above material expect(box, paints..circle(x: 44.0, y: 0.0, color: splashColor)); await gesture.up(); @@ -326,7 +326,7 @@ void main() { ), ), ); - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as RenderBox; expect(box, isNot(paints..rect(color: focusColor))); focusNode.requestFocus(); @@ -425,7 +425,7 @@ void main() { ), ), ); - final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; + final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as RenderBox; final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); expect(box, isNot(paints..rect(color: hoverColor))); diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart index a8046e30802..8426caba736 100644 --- a/packages/flutter/test/material/slider_test.dart +++ b/packages/flutter/test/material/slider_test.dart @@ -1000,7 +1000,7 @@ void main() { ), ); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); // 5 tick marks and a thumb. expect(material, paintsExactlyCountTimes(#drawCircle, 6)); @@ -1056,7 +1056,7 @@ void main() { } Future testReparenting(bool reparent) async { - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); final Offset center = tester.getCenter(find.byType(Slider)); // Move to 0.0. TestGesture gesture = await tester.startGesture(Offset.zero); @@ -2030,7 +2030,7 @@ void main() { await tester.pumpWidget(buildApp()); await tester.pumpAndSettle(); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); // Check that thumb color is using active color. expect(material, paints..circle(color: activeColor)); @@ -2989,7 +2989,7 @@ void main() { await tester.pumpAndSettle(); final MaterialInkController material = - Material.of(tester.element(find.byType(Slider)))!; + Material.of(tester.element(find.byType(Slider))); expect(material, paints..circle(color: color)); }); @@ -3012,7 +3012,7 @@ void main() { await tester.pumpAndSettle(); final MaterialInkController material = - Material.of(tester.element(find.byType(Slider)))!; + Material.of(tester.element(find.byType(Slider))); expect(material, paints..circle(color: color)); }); @@ -3032,7 +3032,7 @@ void main() { await tester.pumpAndSettle(); final MaterialInkController material = - Material.of(tester.element(find.byType(CupertinoSlider)))!; + Material.of(tester.element(find.byType(CupertinoSlider))); expect( material, paints @@ -3064,7 +3064,7 @@ void main() { await tester.pumpAndSettle(); final MaterialInkController material = - Material.of(tester.element(find.byType(CupertinoSlider)))!; + Material.of(tester.element(find.byType(CupertinoSlider))); expect( material, paints..rrect()..rrect()..rrect()..rrect()..rrect()..rrect(color: color), diff --git a/packages/flutter/test/material/slider_theme_test.dart b/packages/flutter/test/material/slider_theme_test.dart index 4ef2451673a..4fb17706909 100644 --- a/packages/flutter/test/material/slider_theme_test.dart +++ b/packages/flutter/test/material/slider_theme_test.dart @@ -163,7 +163,7 @@ void main() { await tester.pumpWidget(buildApp()); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay)); // Check default theme for enabled widget. @@ -356,7 +356,7 @@ void main() { ); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, secondaryTrackValue: 0.75, enabled: false)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); expect( material, @@ -380,7 +380,7 @@ void main() { ); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, secondaryTrackValue: 0.75, enabled: false)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); expect( material, @@ -487,7 +487,7 @@ void main() { final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, secondaryTrackValue: 0.5)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); const Radius radius = Radius.circular(2); const Radius activatedRadius = Radius.circular(3); @@ -523,7 +523,7 @@ void main() { final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); // With no touch, paints only the thumb. expect( @@ -643,7 +643,7 @@ void main() { final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); expect(material, paints..circle(color: sliderTheme.thumbColor, radius: 10.0)); @@ -1050,7 +1050,7 @@ void main() { await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); // Top and bottom are centerY (300) + and - trackRadius (8). expect( @@ -1082,7 +1082,7 @@ void main() { ); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); expect( material, @@ -1106,7 +1106,7 @@ void main() { ); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); expect( material, @@ -1132,7 +1132,7 @@ void main() { await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2)); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); expect( material, @@ -1168,7 +1168,7 @@ void main() { await tester.startGesture(center); await tester.pumpAndSettle(); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); expect( material, paints..circle( @@ -1190,7 +1190,7 @@ void main() { final MaterialInkController material = Material.of( tester.element(find.byType(Slider)), - )!; + ); // The track rectangle begins at 10 pixels from the left of the screen and ends 10 pixels from the right // (790 pixels from the left). The main check here it that the track itself should be centered on @@ -1242,7 +1242,7 @@ void main() { divisions: 4, )); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); expect(material, paintsExactlyCountTimes(#drawRect, 0)); expect(material, paintsExactlyCountTimes(#drawCircle, 0)); @@ -1262,7 +1262,7 @@ void main() { divisions: 4, )); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); // Only 2 track segments. expect(material, paintsExactlyCountTimes(#drawRRect, 2)); @@ -1286,7 +1286,7 @@ void main() { divisions: 4, )); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); // Only 5 tick marks. expect(material, paintsExactlyCountTimes(#drawRect, 0)); @@ -1309,7 +1309,7 @@ void main() { divisions: 4, )); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); // Only 1 thumb. expect(material, paintsExactlyCountTimes(#drawRect, 0)); @@ -1333,7 +1333,7 @@ void main() { divisions: 4, )); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); // Tap the center of the track and wait for animations to finish. final Offset center = tester.getCenter(find.byType(Slider)); @@ -1362,7 +1362,7 @@ void main() { divisions: 4, )); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay)); // Tap the center of the track and wait for animations to finish. @@ -1393,7 +1393,7 @@ void main() { divisions: 4, )); - final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; + final MaterialInkController material = Material.of(tester.element(find.byType(Slider))); final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay)); // Tap the center of the track to kick off the animation of the value indicator. diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 7da052c89fb..48493d4b4f7 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -364,7 +364,7 @@ void main() { expect(find.text('A'), findsOneWidget); expect(find.text('B'), findsOneWidget); expect(find.text('C'), findsOneWidget); - final TabController controller = DefaultTabController.of(tester.element(find.text('A')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('A'))); expect(controller, isNotNull); expect(controller.index, 2); expect(controller.previousIndex, 2); @@ -400,7 +400,7 @@ void main() { expect(find.text('A'), findsOneWidget); expect(find.text('B'), findsOneWidget); expect(find.text('C'), findsOneWidget); - final TabController controller = DefaultTabController.of(tester.element(find.text('A')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('A'))); expect(controller.index, 2); expect(controller.previousIndex, 2); @@ -421,7 +421,7 @@ void main() { final List tabs = ['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'FFFFFF', 'GGGGGG', 'HHHHHH', 'IIIIII', 'JJJJJJ', 'KKKKKK', 'LLLLLL']; const Key tabBarKey = Key('TabBar'); await tester.pumpWidget(buildFrame(tabs: tabs, value: 'AAAAAA', isScrollable: true, tabBarKey: tabBarKey)); - final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA'))); expect(controller, isNotNull); expect(controller.index, 0); @@ -442,7 +442,7 @@ void main() { const Key tabBarKey = Key('TabBar'); const EdgeInsetsGeometry padding = EdgeInsets.only(right: 30, left: 60); await tester.pumpWidget(buildFrame(tabs: tabs, value: 'AAAAAA', isScrollable: true, tabBarKey: tabBarKey, padding: padding)); - final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA'))); expect(controller, isNotNull); expect(controller.index, 0); @@ -470,7 +470,7 @@ void main() { padding: padding, textDirection: TextDirection.rtl, )); - final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA'))); expect(controller, isNotNull); expect(controller.index, 0); @@ -489,7 +489,7 @@ void main() { final List tabs = ['AAAA', 'BBBB', 'CCCC', 'DDDD', 'EEEE', 'FFFF', 'GGGG', 'HHHH', 'IIII', 'JJJJ', 'KKKK', 'LLLL']; const Key tabBarKey = Key('TabBar'); await tester.pumpWidget(buildFrame(tabs: tabs, value: 'AAAA', isScrollable: true, tabBarKey: tabBarKey)); - final TabController controller = DefaultTabController.of(tester.element(find.text('AAAA')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('AAAA'))); expect(controller, isNotNull); expect(controller.index, 0); @@ -529,7 +529,7 @@ void main() { } await tester.pumpWidget(builder()); - final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('AAAAAA'))); TestGesture gesture = await tester.startGesture(tester.getCenter(find.text(tabs[0]))); await gesture.moveBy(const Offset(-600.0, 0.0)); @@ -586,7 +586,7 @@ void main() { expect(find.text('LEFT CHILD'), findsOneWidget); expect(find.text('RIGHT CHILD'), findsNothing); - final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT'))); expect(controller.index, 0); // Fling to the left, switch from the 'LEFT' tab to the 'RIGHT' @@ -615,7 +615,7 @@ void main() { expect(find.text('LEFT CHILD'), findsOneWidget); expect(find.text('RIGHT CHILD'), findsNothing); - final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT'))); expect(controller.index, 0); final Offset flingStart = tester.getCenter(find.text('LEFT CHILD')); @@ -636,7 +636,7 @@ void main() { expect(find.text('LEFT CHILD'), findsOneWidget); expect(find.text('RIGHT CHILD'), findsNothing); - final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT'))); expect(controller.index, 0); final Offset flingStart = tester.getCenter(find.text('LEFT CHILD')); @@ -659,7 +659,7 @@ void main() { expect(find.text('LEFT CHILD'), findsOneWidget); expect(find.text('RIGHT CHILD'), findsNothing); - final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT'))); expect(controller.index, 0); final Offset flingStart = tester.getCenter(find.text('LEFT CHILD')); @@ -772,7 +772,7 @@ void main() { final List tabs = ['LEFT', 'RIGHT']; await tester.pumpWidget(buildLeftRightApp(tabs: tabs, value: 'LEFT')); - final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('LEFT'))); expect(controller, isNotNull); expect(controller.index, 0); @@ -1013,7 +1013,7 @@ void main() { final List tabs = ['A', 'B', 'C']; await tester.pumpWidget(buildFrame(tabs: tabs, value: 'B', animationDuration: animationDuration)); - final TabController controller = DefaultTabController.of(tester.element(find.text('A')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('A'))); await tester.tap(find.text('A')); await tester.pump(); @@ -1241,7 +1241,7 @@ void main() { final List tabs = ['A', 'B', 'C']; await tester.pumpWidget(buildFrame(tabs: tabs, value: 'B', animationDuration: Duration.zero)); - final TabController controller = DefaultTabController.of(tester.element(find.text('A')))!; + final TabController controller = DefaultTabController.of(tester.element(find.text('A'))); await tester.tap(find.text('A')); await tester.pump(); @@ -1288,7 +1288,7 @@ void main() { } await tester.pumpWidget(buildWithTabBarView()); - TabController controller = DefaultTabController.of(tester.element(find.text('A')))!; + TabController controller = DefaultTabController.of(tester.element(find.text('A'))); expect(controller.index, 0); tabs.add('B'); @@ -1297,7 +1297,7 @@ void main() { await tester.pumpWidget(buildWithTabBarView()); await tester.tap(find.text('C')); await tester.pumpAndSettle(); - controller = DefaultTabController.of(tester.element(find.text('A')))!; + controller = DefaultTabController.of(tester.element(find.text('A'))); expect(controller.index, 2); expect(tester.takeException(), isNull); @@ -3752,7 +3752,7 @@ void main() { ), ); } - TabController getController() => DefaultTabController.of(tester.element(find.text('A')))!; + TabController getController() => DefaultTabController.of(tester.element(find.text('A'))); await tester.pumpWidget(buildTabs(threeTabs)); await tester.tap(find.text('B')); @@ -4346,7 +4346,7 @@ void main() { ), ); - TabController getController() => DefaultTabController.of(tester.element(find.text('B')))!; + TabController getController() => DefaultTabController.of(tester.element(find.text('B'))); TabController controller = getController(); controller.animateTo(2, duration: const Duration(milliseconds: 200), curve: Curves.linear); diff --git a/packages/flutter/test/material/text_button_test.dart b/packages/flutter/test/material/text_button_test.dart index c91e4345af3..17fd1029b49 100644 --- a/packages/flutter/test/material/text_button_test.dart +++ b/packages/flutter/test/material/text_button_test.dart @@ -1249,7 +1249,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: NoSplash.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 0)); await gesture.up(); @@ -1260,7 +1260,7 @@ void main() { await tester.pumpWidget(buildFrame(splashFactory: InkRipple.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); - final MaterialInkController material = Material.of(tester.element(find.text('test')))!; + final MaterialInkController material = Material.of(tester.element(find.text('test'))); await tester.pump(const Duration(milliseconds: 200)); expect(material, paintsExactlyCountTimes(#drawCircle, 1)); await gesture.up(); diff --git a/packages/flutter/test/widgets/default_text_height_behavior_test.dart b/packages/flutter/test/widgets/default_text_height_behavior_test.dart index 3817f8025e9..a3974dffb09 100644 --- a/packages/flutter/test/widgets/default_text_height_behavior_test.dart +++ b/packages/flutter/test/widgets/default_text_height_behavior_test.dart @@ -116,7 +116,7 @@ void main() { await tester.pumpWidget(Builder( builder: (BuildContext context) { - textHeightBehavior = DefaultTextHeightBehavior.of(context); + textHeightBehavior = DefaultTextHeightBehavior.maybeOf(context); return textWidget; }, )); diff --git a/packages/flutter/test/widgets/linked_scroll_view_test.dart b/packages/flutter/test/widgets/linked_scroll_view_test.dart index af0bd3eca57..d1263d08efe 100644 --- a/packages/flutter/test/widgets/linked_scroll_view_test.dart +++ b/packages/flutter/test/widgets/linked_scroll_view_test.dart @@ -286,8 +286,8 @@ class _TestState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - _beforeController.setParent(PrimaryScrollController.of(context)); - _afterController.setParent(PrimaryScrollController.of(context)); + _beforeController.setParent(PrimaryScrollController.maybeOf(context)); + _afterController.setParent(PrimaryScrollController.maybeOf(context)); } @override diff --git a/packages/flutter/test/widgets/list_view_misc_test.dart b/packages/flutter/test/widgets/list_view_misc_test.dart index d20a5c52960..42c216c8966 100644 --- a/packages/flutter/test/widgets/list_view_misc_test.dart +++ b/packages/flutter/test/widgets/list_view_misc_test.dart @@ -215,7 +215,7 @@ void main() { await tester.pumpWidget(buildFrame()); expect(find.text('top'), findsOneWidget); - final ScrollPosition position = Scrollable.of(tester.element(find.text('middle')))!.position; + final ScrollPosition position = Scrollable.of(tester.element(find.text('middle'))).position; expect(position.viewportDimension, 600.0); expect(position.pixels, 0.0); diff --git a/packages/flutter/test/widgets/page_storage_test.dart b/packages/flutter/test/widgets/page_storage_test.dart index 5025f765cd0..db74aa58b32 100644 --- a/packages/flutter/test/widgets/page_storage_test.dart +++ b/packages/flutter/test/widgets/page_storage_test.dart @@ -16,7 +16,7 @@ void main() { home: StatefulBuilder( key: builderKey, builder: (BuildContext context, StateSetter setter) { - PageStorage.of(context)!.writeState(context, storedValue); + PageStorage.of(context).writeState(context, storedValue); setState = setter; return Center( child: Text('storedValue: $storedValue'), @@ -28,13 +28,13 @@ void main() { final Element builderElement = tester.element(find.byKey(builderKey)); expect(PageStorage.of(builderElement), isNotNull); - expect(PageStorage.of(builderElement)!.readState(builderElement), equals(storedValue)); + expect(PageStorage.of(builderElement).readState(builderElement), equals(storedValue)); setState(() { storedValue = 1; }); await tester.pump(); - expect(PageStorage.of(builderElement)!.readState(builderElement), equals(storedValue)); + expect(PageStorage.of(builderElement).readState(builderElement), equals(storedValue)); }); testWidgets('PageStorage read and write by identifier', (WidgetTester tester) async { @@ -46,7 +46,7 @@ void main() { home: StatefulBuilder( key: key, builder: (BuildContext context, StateSetter setter) { - PageStorage.of(context)!.writeState(context, storedValue, identifier: 123); + PageStorage.of(context).writeState(context, storedValue, identifier: 123); setState = setter; return Center( child: Text('storedValue: $storedValue'), @@ -60,8 +60,8 @@ void main() { await tester.pumpWidget(buildWidthKey(key)); Element builderElement = tester.element(find.byKey(key)); expect(PageStorage.of(builderElement), isNotNull); - expect(PageStorage.of(builderElement)!.readState(builderElement), isNull); - expect(PageStorage.of(builderElement)!.readState(builderElement, identifier: 123), equals(storedValue)); + expect(PageStorage.of(builderElement).readState(builderElement), isNull); + expect(PageStorage.of(builderElement).readState(builderElement, identifier: 123), equals(storedValue)); // New StatefulBuilder widget - different key - but the same PageStorage identifier. @@ -69,14 +69,14 @@ void main() { await tester.pumpWidget(buildWidthKey(key)); builderElement = tester.element(find.byKey(key)); expect(PageStorage.of(builderElement), isNotNull); - expect(PageStorage.of(builderElement)!.readState(builderElement), isNull); - expect(PageStorage.of(builderElement)!.readState(builderElement, identifier: 123), equals(storedValue)); + expect(PageStorage.of(builderElement).readState(builderElement), isNull); + expect(PageStorage.of(builderElement).readState(builderElement, identifier: 123), equals(storedValue)); setState(() { storedValue = 1; }); await tester.pump(); - expect(PageStorage.of(builderElement)!.readState(builderElement, identifier: 123), equals(storedValue)); + expect(PageStorage.of(builderElement).readState(builderElement, identifier: 123), equals(storedValue)); }); } diff --git a/packages/flutter/test/widgets/restoration.dart b/packages/flutter/test/widgets/restoration.dart index d6ac7af4300..7aa79f4766d 100644 --- a/packages/flutter/test/widgets/restoration.dart +++ b/packages/flutter/test/widgets/restoration.dart @@ -21,7 +21,7 @@ class BucketSpyState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - bucket = RestorationScope.of(context); + bucket = RestorationScope.maybeOf(context); } @override diff --git a/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart b/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart index 919082c9fa8..a0e2be0bbb4 100644 --- a/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart +++ b/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart @@ -22,11 +22,11 @@ void main() { }); T findPhysics(WidgetTester tester) { - return Scrollable.of(find.byType(TestWidget).evaluate().first)!.position.physics as T; + return Scrollable.of(find.byType(TestWidget).evaluate().first).position.physics as T; } ScrollMetrics findMetrics(WidgetTester tester) { - return Scrollable.of(find.byType(TestWidget).evaluate().first)!.position; + return Scrollable.of(find.byType(TestWidget).evaluate().first).position; } testWidgets('ScrollAwareImageProvider does not delay if widget is not in scrollable', (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/scroll_behavior_test.dart b/packages/flutter/test/widgets/scroll_behavior_test.dart index a6cbd6428e0..99ce61f0f77 100644 --- a/packages/flutter/test/widgets/scroll_behavior_test.dart +++ b/packages/flutter/test/widgets/scroll_behavior_test.dart @@ -50,7 +50,7 @@ void main() { child: Builder( builder: (BuildContext context) { behavior = ScrollConfiguration.of(context) as TestScrollBehavior; - position = Scrollable.of(context)!.position as ScrollPositionWithSingleContext; + position = Scrollable.of(context).position as ScrollPositionWithSingleContext; return Container(height: 1000.0); }, ), diff --git a/packages/flutter/test/widgets/scroll_notification_test.dart b/packages/flutter/test/widgets/scroll_notification_test.dart index 97928f67b74..afcd3a58b6e 100644 --- a/packages/flutter/test/widgets/scroll_notification_test.dart +++ b/packages/flutter/test/widgets/scroll_notification_test.dart @@ -218,7 +218,7 @@ void main() { ScrollNotificationObserver( child: Builder( builder: (BuildContext context) { - observer = ScrollNotificationObserver.of(context)!; + observer = ScrollNotificationObserver.of(context); return const SingleChildScrollView( child: SizedBox(height: 1200.0), ); diff --git a/packages/flutter/test/widgets/scroll_view_test.dart b/packages/flutter/test/widgets/scroll_view_test.dart index ee5b121746d..be22bf40741 100644 --- a/packages/flutter/test/widgets/scroll_view_test.dart +++ b/packages/flutter/test/widgets/scroll_view_test.dart @@ -1478,7 +1478,7 @@ void main() { ); final ScrollController controller = PrimaryScrollController.of( tester.element(find.byType(CustomScrollView)), - )!; + ); await tester.pumpAndSettle(); expect(controller.position.pixels, equals(0.0)); expect( diff --git a/packages/flutter/test/widgets/scrollable_of_test.dart b/packages/flutter/test/widgets/scrollable_of_test.dart index 2f7489e3307..bae7633ddf8 100644 --- a/packages/flutter/test/widgets/scrollable_of_test.dart +++ b/packages/flutter/test/widgets/scrollable_of_test.dart @@ -22,7 +22,7 @@ class _ScrollPositionListenerState extends State { void didChangeDependencies() { super.didChangeDependencies(); _position?.removeListener(listener); - _position = Scrollable.of(context)?.position; + _position = Scrollable.maybeOf(context)?.position; _position?.addListener(listener); widget.log('didChangeDependencies ${_position?.pixels.toStringAsFixed(1)}'); } diff --git a/packages/flutter/test/widgets/scrollable_test.dart b/packages/flutter/test/widgets/scrollable_test.dart index edd056d40b9..12b1839e034 100644 --- a/packages/flutter/test/widgets/scrollable_test.dart +++ b/packages/flutter/test/widgets/scrollable_test.dart @@ -941,7 +941,7 @@ void main() { // Getting the tester to simulate a life-like fling is difficult. // Instead, just manually drive the activity with a ballistic simulation as // if the user has flung the list. - Scrollable.of(find.byType(SizedBox).evaluate().first)!.position.activity!.delegate.goBallistic(4000); + Scrollable.of(find.byType(SizedBox).evaluate().first).position.activity!.delegate.goBallistic(4000); await tester.pumpAndSettle(); expect(find.byKey(const ValueKey('Box 0')), findsNothing); @@ -970,7 +970,7 @@ void main() { )); await tester.pumpAndSettle(); - final ScrollPosition position = Scrollable.of(find.byType(SizedBox).evaluate().first)!.position; + final ScrollPosition position = Scrollable.of(find.byType(SizedBox).evaluate().first).position; final SuperPessimisticScrollPhysics physics = position.physics as SuperPessimisticScrollPhysics; expect(find.byKey(const ValueKey('Box 0')), findsOneWidget); @@ -1014,7 +1014,7 @@ void main() { )); await tester.pumpAndSettle(); - final ScrollPosition position = Scrollable.of(find.byType(SizedBox).evaluate().first)!.position; + final ScrollPosition position = Scrollable.of(find.byType(SizedBox).evaluate().first).position; expect(find.byKey(const ValueKey('Cheap box 0')), findsOneWidget); expect(find.byKey(const ValueKey('Cheap box 52')), findsNothing); diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart index c566945227a..4c314c14405 100644 --- a/packages/flutter/test/widgets/shortcuts_test.dart +++ b/packages/flutter/test/widgets/shortcuts_test.dart @@ -1028,7 +1028,7 @@ void main() { ); final ScrollController controller = PrimaryScrollController.of( tester.element(find.byType(ListView)), - )!; + ); expect(controller.position.pixels, 0.0); expect(value, isTrue);