diff --git a/packages/flutter/lib/src/cupertino/localizations.dart b/packages/flutter/lib/src/cupertino/localizations.dart index 40302f14b9..4c145b3fd5 100644 --- a/packages/flutter/lib/src/cupertino/localizations.dart +++ b/packages/flutter/lib/src/cupertino/localizations.dart @@ -259,7 +259,7 @@ abstract class CupertinoLocalizations { /// CupertinoLocalizations.of(context).anteMeridiemAbbreviation; /// ``` static CupertinoLocalizations of(BuildContext context) { - debugCheckHasCupertinoLocalizations(context); + assert(debugCheckHasCupertinoLocalizations(context)); return Localizations.of(context, CupertinoLocalizations)!; } } diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart index 5af8df6b40..86b6aa6b45 100644 --- a/packages/flutter/lib/src/material/material_localizations.dart +++ b/packages/flutter/lib/src/material/material_localizations.dart @@ -506,7 +506,7 @@ abstract class MaterialLocalizations { /// tooltip: MaterialLocalizations.of(context).backButtonTooltip, /// ``` static MaterialLocalizations of(BuildContext context) { - debugCheckHasMaterialLocalizations(context); + assert(debugCheckHasMaterialLocalizations(context)); return Localizations.of(context, MaterialLocalizations)!; } } diff --git a/packages/flutter/lib/src/widgets/debug.dart b/packages/flutter/lib/src/widgets/debug.dart index 2a98259f92..083a12b6dd 100644 --- a/packages/flutter/lib/src/widgets/debug.dart +++ b/packages/flutter/lib/src/widgets/debug.dart @@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart'; import 'basic.dart'; import 'framework.dart'; +import 'localizations.dart'; import 'media_query.dart'; import 'table.dart'; @@ -321,6 +322,44 @@ void debugWidgetBuilderValue(Widget widget, Widget? built) { }()); } +/// Asserts that the given context has a [Localizations] ancestor that contains +/// a [WidgetsLocalizations] delegate. +/// +/// To call this function, use the following pattern, typically in the +/// relevant Widget's build method: +/// +/// ```dart +/// assert(debugCheckHasWidgetsLocalizations(context)); +/// ``` +/// +/// Does nothing if asserts are disabled. Always returns true. +bool debugCheckHasWidgetsLocalizations(BuildContext context) { + assert(() { + if (Localizations.of(context, WidgetsLocalizations) == null) { + throw FlutterError.fromParts([ + ErrorSummary('No WidgetsLocalizations found.'), + ErrorDescription( + '${context.widget.runtimeType} widgets require WidgetsLocalizations ' + 'to be provided by a Localizations widget ancestor.' + ), + ErrorDescription( + 'The widgets library uses Localizations to generate messages, ' + 'labels, and abbreviations.' + ), + ErrorHint( + 'To introduce a WidgetsLocalizations, either use a ' + 'WidgetsApp at the root of your application to include them ' + 'automatically, or add a Localization widget with a ' + 'WidgetsLocalizations delegate.' + ), + ...context.describeMissingAncestor(expectedAncestorType: WidgetsLocalizations) + ]); + } + return true; + }()); + return true; +} + /// Returns true if none of the widget library debug variables have been changed. /// /// This function is used by the test framework to ensure that debug variables diff --git a/packages/flutter/lib/src/widgets/localizations.dart b/packages/flutter/lib/src/widgets/localizations.dart index 8de4803a9e..f8ea7ea2b7 100644 --- a/packages/flutter/lib/src/widgets/localizations.dart +++ b/packages/flutter/lib/src/widgets/localizations.dart @@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart'; import 'basic.dart'; import 'binding.dart'; import 'container.dart'; +import 'debug.dart'; import 'framework.dart'; // Examples can assume: @@ -155,7 +156,7 @@ abstract class WidgetsLocalizations { /// that encloses the given context. /// /// This method is just a convenient shorthand for: - /// `Localizations.of(context, WidgetsLocalizations)`. + /// `Localizations.of(context, WidgetsLocalizations)!`. /// /// References to the localized resources defined by this class are typically /// written in terms of this method. For example: @@ -163,8 +164,9 @@ abstract class WidgetsLocalizations { /// ```dart /// textDirection: WidgetsLocalizations.of(context).textDirection, /// ``` - static WidgetsLocalizations? of(BuildContext context) { - return Localizations.of(context, WidgetsLocalizations); + static WidgetsLocalizations of(BuildContext context) { + assert(debugCheckHasWidgetsLocalizations(context)); + return Localizations.of(context, WidgetsLocalizations)!; } } diff --git a/packages/flutter/test/widgets/debug_test.dart b/packages/flutter/test/widgets/debug_test.dart index f154b6118a..c6f5a59179 100644 --- a/packages/flutter/test/widgets/debug_test.dart +++ b/packages/flutter/test/widgets/debug_test.dart @@ -215,6 +215,33 @@ void main() { } }); + testWidgets('debugCheckHasWidgetsLocalizations throws', (WidgetTester tester) async { + final GlobalKey noLocalizationsAvailable = GlobalKey(); + final GlobalKey localizationsAvailable = GlobalKey(); + + await tester.pumpWidget( + Container( + key: noLocalizationsAvailable, + child: WidgetsApp( + builder: (BuildContext context, Widget? child) { + return Container( + key: localizationsAvailable, + ); + }, + color: const Color(0xFF4CAF50), + ), + ), + ); + + expect(() => debugCheckHasWidgetsLocalizations(noLocalizationsAvailable.currentContext!), throwsA(isAssertionError.having( + (AssertionError e) => e.message, + 'message', + contains('No WidgetsLocalizations found'), + ))); + + expect(debugCheckHasWidgetsLocalizations(localizationsAvailable.currentContext!), isTrue); + }); + test('debugAssertAllWidgetVarsUnset', () { debugHighlightDeprecatedWidgets = true; late FlutterError error;