1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:collection' show HashMap;
6
7import 'package:flutter/foundation.dart';
8import 'package:flutter/rendering.dart';
9import 'package:flutter/services.dart';
10
11import 'actions.dart';
12import 'banner.dart';
13import 'basic.dart';
14import 'binding.dart';
15import 'default_text_editing_shortcuts.dart';
16import 'focus_scope.dart';
17import 'focus_traversal.dart';
18import 'framework.dart';
19import 'localizations.dart';
20import 'media_query.dart';
21import 'navigator.dart';
22import 'notification_listener.dart';
23import 'pages.dart';
24import 'performance_overlay.dart';
25import 'restoration.dart';
26import 'router.dart';
27import 'scrollable_helpers.dart';
28import 'semantics_debugger.dart';
29import 'shared_app_data.dart';
30import 'shortcuts.dart';
31import 'tap_region.dart';
32import 'text.dart';
33import 'title.dart';
34import 'value_listenable_builder.dart';
35import 'widget_inspector.dart';
36
37export 'dart:ui' show Locale;
38
39// Examples can assume:
40// late Widget myWidget;
41
42/// The signature of [WidgetsApp.localeListResolutionCallback].
43///
44/// A [LocaleListResolutionCallback] is responsible for computing the locale of the app's
45/// [Localizations] object when the app starts and when user changes the list of
46/// locales for the device.
47///
48/// The [locales] list is the device's preferred locales when the app started, or the
49/// device's preferred locales the user selected after the app was started. This list
50/// is in order of preference. If this list is null or empty, then Flutter has not yet
51/// received the locale information from the platform. The [supportedLocales] parameter
52/// is just the value of [WidgetsApp.supportedLocales].
53///
54/// See also:
55///
56/// * [LocaleResolutionCallback], which takes only one default locale (instead of a list)
57/// and is attempted only after this callback fails or is null. [LocaleListResolutionCallback]
58/// is recommended over [LocaleResolutionCallback].
59typedef LocaleListResolutionCallback = Locale? Function(List<Locale>? locales, Iterable<Locale> supportedLocales);
60
61/// {@template flutter.widgets.LocaleResolutionCallback}
62/// The signature of [WidgetsApp.localeResolutionCallback].
63///
64/// It is recommended to provide a [LocaleListResolutionCallback] instead of a
65/// [LocaleResolutionCallback] when possible, as [LocaleResolutionCallback] only
66/// receives a subset of the information provided in [LocaleListResolutionCallback].
67///
68/// A [LocaleResolutionCallback] is responsible for computing the locale of the app's
69/// [Localizations] object when the app starts and when user changes the default
70/// locale for the device after [LocaleListResolutionCallback] fails or is not provided.
71///
72/// This callback is also used if the app is created with a specific locale using
73/// the [WidgetsApp.new] `locale` parameter.
74///
75/// The [locale] is either the value of [WidgetsApp.locale], or the device's default
76/// locale when the app started, or the device locale the user selected after the app
77/// was started. The default locale is the first locale in the list of preferred
78/// locales. If [locale] is null, then Flutter has not yet received the locale
79/// information from the platform. The [supportedLocales] parameter is just the value of
80/// [WidgetsApp.supportedLocales].
81///
82/// See also:
83///
84/// * [LocaleListResolutionCallback], which takes a list of preferred locales (instead of one locale).
85/// Resolutions by [LocaleListResolutionCallback] take precedence over [LocaleResolutionCallback].
86/// {@endtemplate}
87typedef LocaleResolutionCallback = Locale? Function(Locale? locale, Iterable<Locale> supportedLocales);
88
89/// The default locale resolution algorithm.
90///
91/// Custom resolution algorithms can be provided through
92/// [WidgetsApp.localeListResolutionCallback] or
93/// [WidgetsApp.localeResolutionCallback].
94///
95/// When no custom locale resolution algorithms are provided or if both fail
96/// to resolve, Flutter will default to calling this algorithm.
97///
98/// This algorithm prioritizes speed at the cost of slightly less appropriate
99/// resolutions for edge cases.
100///
101/// This algorithm will resolve to the earliest preferred locale that
102/// matches the most fields, prioritizing in the order of perfect match,
103/// languageCode+countryCode, languageCode+scriptCode, languageCode-only.
104///
105/// In the case where a locale is matched by languageCode-only and is not the
106/// default (first) locale, the next preferred locale with a
107/// perfect match can supersede the languageCode-only match if it exists.
108///
109/// When a preferredLocale matches more than one supported locale, it will
110/// resolve to the first matching locale listed in the supportedLocales.
111///
112/// When all preferred locales have been exhausted without a match, the first
113/// countryCode only match will be returned.
114///
115/// When no match at all is found, the first (default) locale in
116/// [supportedLocales] will be returned.
117///
118/// To summarize, the main matching priority is:
119///
120/// 1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode]
121/// 2. [Locale.languageCode] and [Locale.scriptCode] only
122/// 3. [Locale.languageCode] and [Locale.countryCode] only
123/// 4. [Locale.languageCode] only (with caveats, see above)
124/// 5. [Locale.countryCode] only when all [preferredLocales] fail to match
125/// 6. Returns the first element of [supportedLocales] as a fallback
126///
127/// This algorithm does not take language distance (how similar languages are to each other)
128/// into account, and will not handle edge cases such as resolving `de` to `fr` rather than `zh`
129/// when `de` is not supported and `zh` is listed before `fr` (German is closer to French
130/// than Chinese).
131Locale basicLocaleListResolution(List<Locale>? preferredLocales, Iterable<Locale> supportedLocales) {
132 // preferredLocales can be null when called before the platform has had a chance to
133 // initialize the locales. Platforms without locale passing support will provide an empty list.
134 // We default to the first supported locale in these cases.
135 if (preferredLocales == null || preferredLocales.isEmpty) {
136 return supportedLocales.first;
137 }
138 // Hash the supported locales because apps can support many locales and would
139 // be expensive to search through them many times.
140 final Map<String, Locale> allSupportedLocales = HashMap<String, Locale>();
141 final Map<String, Locale> languageAndCountryLocales = HashMap<String, Locale>();
142 final Map<String, Locale> languageAndScriptLocales = HashMap<String, Locale>();
143 final Map<String, Locale> languageLocales = HashMap<String, Locale>();
144 final Map<String?, Locale> countryLocales = HashMap<String?, Locale>();
145 for (final Locale locale in supportedLocales) {
146 allSupportedLocales['${locale.languageCode}_${locale.scriptCode}_${locale.countryCode}'] ??= locale;
147 languageAndScriptLocales['${locale.languageCode}_${locale.scriptCode}'] ??= locale;
148 languageAndCountryLocales['${locale.languageCode}_${locale.countryCode}'] ??= locale;
149 languageLocales[locale.languageCode] ??= locale;
150 countryLocales[locale.countryCode] ??= locale;
151 }
152
153 // Since languageCode-only matches are possibly low quality, we don't return
154 // it instantly when we find such a match. We check to see if the next
155 // preferred locale in the list has a high accuracy match, and only return
156 // the languageCode-only match when a higher accuracy match in the next
157 // preferred locale cannot be found.
158 Locale? matchesLanguageCode;
159 Locale? matchesCountryCode;
160 // Loop over user's preferred locales
161 for (int localeIndex = 0; localeIndex < preferredLocales.length; localeIndex += 1) {
162 final Locale userLocale = preferredLocales[localeIndex];
163 // Look for perfect match.
164 if (allSupportedLocales.containsKey('${userLocale.languageCode}_${userLocale.scriptCode}_${userLocale.countryCode}')) {
165 return userLocale;
166 }
167 // Look for language+script match.
168 if (userLocale.scriptCode != null) {
169 final Locale? match = languageAndScriptLocales['${userLocale.languageCode}_${userLocale.scriptCode}'];
170 if (match != null) {
171 return match;
172 }
173 }
174 // Look for language+country match.
175 if (userLocale.countryCode != null) {
176 final Locale? match = languageAndCountryLocales['${userLocale.languageCode}_${userLocale.countryCode}'];
177 if (match != null) {
178 return match;
179 }
180 }
181 // If there was a languageCode-only match in the previous iteration's higher
182 // ranked preferred locale, we return it if the current userLocale does not
183 // have a better match.
184 if (matchesLanguageCode != null) {
185 return matchesLanguageCode;
186 }
187 // Look and store language-only match.
188 Locale? match = languageLocales[userLocale.languageCode];
189 if (match != null) {
190 matchesLanguageCode = match;
191 // Since first (default) locale is usually highly preferred, we will allow
192 // a languageCode-only match to be instantly matched. If the next preferred
193 // languageCode is the same, we defer hastily returning until the next iteration
194 // since at worst it is the same and at best an improved match.
195 if (localeIndex == 0 &&
196 !(localeIndex + 1 < preferredLocales.length && preferredLocales[localeIndex + 1].languageCode == userLocale.languageCode)) {
197 return matchesLanguageCode;
198 }
199 }
200 // countryCode-only match. When all else except default supported locale fails,
201 // attempt to match by country only, as a user is likely to be familiar with a
202 // language from their listed country.
203 if (matchesCountryCode == null && userLocale.countryCode != null) {
204 match = countryLocales[userLocale.countryCode];
205 if (match != null) {
206 matchesCountryCode = match;
207 }
208 }
209 }
210 // When there is no languageCode-only match. Fallback to matching countryCode only. Country
211 // fallback only applies on iOS. When there is no countryCode-only match, we return first
212 // supported locale.
213 final Locale resolvedLocale = matchesLanguageCode ?? matchesCountryCode ?? supportedLocales.first;
214 return resolvedLocale;
215}
216
217/// The signature of [WidgetsApp.onGenerateTitle].
218///
219/// Used to generate a value for the app's [Title.title], which the device uses
220/// to identify the app for the user. The `context` includes the [WidgetsApp]'s
221/// [Localizations] widget so that this method can be used to produce a
222/// localized title.
223///
224/// This function must not return null.
225typedef GenerateAppTitle = String Function(BuildContext context);
226
227/// The signature of [WidgetsApp.pageRouteBuilder].
228///
229/// Creates a [PageRoute] using the given [RouteSettings] and [WidgetBuilder].
230typedef PageRouteFactory = PageRoute<T> Function<T>(RouteSettings settings, WidgetBuilder builder);
231
232/// The signature of [WidgetsApp.onGenerateInitialRoutes].
233///
234/// Creates a series of one or more initial routes.
235typedef InitialRouteListFactory = List<Route<dynamic>> Function(String initialRoute);
236
237/// A convenience widget that wraps a number of widgets that are commonly
238/// required for an application.
239///
240/// One of the primary roles that [WidgetsApp] provides is binding the system
241/// back button to popping the [Navigator] or quitting the application.
242///
243/// It is used by both [MaterialApp] and [CupertinoApp] to implement base
244/// functionality for an app.
245///
246/// Find references to many of the widgets that [WidgetsApp] wraps in the "See
247/// also" section.
248///
249/// See also:
250///
251/// * [CheckedModeBanner], which displays a [Banner] saying "DEBUG" when
252/// running in debug mode.
253/// * [DefaultTextStyle], the text style to apply to descendant [Text] widgets
254/// without an explicit style.
255/// * [MediaQuery], which establishes a subtree in which media queries resolve
256/// to a [MediaQueryData].
257/// * [Localizations], which defines the [Locale] for its `child`.
258/// * [Title], a widget that describes this app in the operating system.
259/// * [Navigator], a widget that manages a set of child widgets with a stack
260/// discipline.
261/// * [Overlay], a widget that manages a [Stack] of entries that can be managed
262/// independently.
263/// * [SemanticsDebugger], a widget that visualizes the semantics for the child.
264class WidgetsApp extends StatefulWidget {
265 /// Creates a widget that wraps a number of widgets that are commonly
266 /// required for an application.
267 ///
268 /// Most callers will want to use the [home] or [routes] parameters, or both.
269 /// The [home] parameter is a convenience for the following [routes] map:
270 ///
271 /// ```dart
272 /// <String, WidgetBuilder>{ '/': (BuildContext context) => myWidget }
273 /// ```
274 ///
275 /// It is possible to specify both [home] and [routes], but only if [routes] does
276 /// _not_ contain an entry for `'/'`. Conversely, if [home] is omitted, [routes]
277 /// _must_ contain an entry for `'/'`.
278 ///
279 /// If [home] or [routes] are not null, the routing implementation needs to know how
280 /// appropriately build [PageRoute]s. This can be achieved by supplying the
281 /// [pageRouteBuilder] parameter. The [pageRouteBuilder] is used by [MaterialApp]
282 /// and [CupertinoApp] to create [MaterialPageRoute]s and [CupertinoPageRoute],
283 /// respectively.
284 ///
285 /// The [builder] parameter is designed to provide the ability to wrap the visible
286 /// content of the app in some other widget. It is recommended that you use [home]
287 /// rather than [builder] if you intend to only display a single route in your app.
288 ///
289 /// [WidgetsApp] is also possible to provide a custom implementation of routing via the
290 /// [onGenerateRoute] and [onUnknownRoute] parameters. These parameters correspond
291 /// to [Navigator.onGenerateRoute] and [Navigator.onUnknownRoute]. If [home], [routes],
292 /// and [builder] are null, or if they fail to create a requested route,
293 /// [onGenerateRoute] will be invoked. If that fails, [onUnknownRoute] will be invoked.
294 ///
295 /// The [pageRouteBuilder] is called to create a [PageRoute] that wraps newly built routes.
296 /// If the [builder] is non-null and the [onGenerateRoute] argument is null, then the
297 /// [builder] will be provided only with the context and the child widget, whereas
298 /// the [pageRouteBuilder] will be provided with [RouteSettings]; in that configuration,
299 /// the [navigatorKey], [onUnknownRoute], [navigatorObservers], and
300 /// [initialRoute] properties must have their default values, as they will have no effect.
301 ///
302 /// The `supportedLocales` argument must be a list of one or more elements.
303 /// By default supportedLocales is `[const Locale('en', 'US')]`.
304 ///
305 /// {@tool dartpad}
306 /// This sample shows a basic Flutter application using [WidgetsApp].
307 ///
308 /// ** See code in examples/api/lib/widgets/app/widgets_app.widgets_app.0.dart **
309 /// {@end-tool}
310 WidgetsApp({ // can't be const because the asserts use methods on Iterable :-(
311 super.key,
312 this.navigatorKey,
313 this.onGenerateRoute,
314 this.onGenerateInitialRoutes,
315 this.onUnknownRoute,
316 this.onNavigationNotification,
317 List<NavigatorObserver> this.navigatorObservers = const <NavigatorObserver>[],
318 this.initialRoute,
319 this.pageRouteBuilder,
320 this.home,
321 Map<String, WidgetBuilder> this.routes = const <String, WidgetBuilder>{},
322 this.builder,
323 this.title = '',
324 this.onGenerateTitle,
325 this.textStyle,
326 required this.color,
327 this.locale,
328 this.localizationsDelegates,
329 this.localeListResolutionCallback,
330 this.localeResolutionCallback,
331 this.supportedLocales = const <Locale>[Locale('en', 'US')],
332 this.showPerformanceOverlay = false,
333 this.checkerboardRasterCacheImages = false,
334 this.checkerboardOffscreenLayers = false,
335 this.showSemanticsDebugger = false,
336 this.debugShowWidgetInspector = false,
337 this.debugShowCheckedModeBanner = true,
338 this.inspectorSelectButtonBuilder,
339 this.shortcuts,
340 this.actions,
341 this.restorationScopeId,
342 @Deprecated(
343 'Remove this parameter as it is now ignored. '
344 'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. '
345 'This feature was deprecated after v3.7.0-29.0.pre.'
346 )
347 this.useInheritedMediaQuery = false,
348 }) : assert(
349 home == null ||
350 onGenerateInitialRoutes == null,
351 'If onGenerateInitialRoutes is specified, the home argument will be '
352 'redundant.',
353 ),
354 assert(
355 home == null ||
356 !routes.containsKey(Navigator.defaultRouteName),
357 'If the home property is specified, the routes table '
358 'cannot include an entry for "/", since it would be redundant.',
359 ),
360 assert(
361 builder != null ||
362 home != null ||
363 routes.containsKey(Navigator.defaultRouteName) ||
364 onGenerateRoute != null ||
365 onUnknownRoute != null,
366 'Either the home property must be specified, '
367 'or the routes table must include an entry for "/", '
368 'or there must be on onGenerateRoute callback specified, '
369 'or there must be an onUnknownRoute callback specified, '
370 'or the builder property must be specified, '
371 'because otherwise there is nothing to fall back on if the '
372 'app is started with an intent that specifies an unknown route.',
373 ),
374 assert(
375 (home != null ||
376 routes.isNotEmpty ||
377 onGenerateRoute != null ||
378 onUnknownRoute != null)
379 ||
380 (builder != null &&
381 navigatorKey == null &&
382 initialRoute == null &&
383 navigatorObservers.isEmpty),
384 'If no route is provided using '
385 'home, routes, onGenerateRoute, or onUnknownRoute, '
386 'a non-null callback for the builder property must be provided, '
387 'and the other navigator-related properties, '
388 'navigatorKey, initialRoute, and navigatorObservers, '
389 'must have their initial values '
390 '(null, null, and the empty list, respectively).',
391 ),
392 assert(
393 builder != null ||
394 onGenerateRoute != null ||
395 pageRouteBuilder != null,
396 'If neither builder nor onGenerateRoute are provided, the '
397 'pageRouteBuilder must be specified so that the default handler '
398 'will know what kind of PageRoute transition to build.',
399 ),
400 assert(supportedLocales.isNotEmpty),
401 routeInformationProvider = null,
402 routeInformationParser = null,
403 routerDelegate = null,
404 backButtonDispatcher = null,
405 routerConfig = null;
406
407 /// Creates a [WidgetsApp] that uses the [Router] instead of a [Navigator].
408 ///
409 /// {@template flutter.widgets.WidgetsApp.router}
410 /// If the [routerConfig] is provided, the other router related delegates,
411 /// [routeInformationParser], [routeInformationProvider], [routerDelegate],
412 /// and [backButtonDispatcher], must all be null.
413 /// {@endtemplate}
414 WidgetsApp.router({
415 super.key,
416 this.routeInformationProvider,
417 this.routeInformationParser,
418 this.routerDelegate,
419 this.routerConfig,
420 this.backButtonDispatcher,
421 this.builder,
422 this.title = '',
423 this.onGenerateTitle,
424 this.onNavigationNotification,
425 this.textStyle,
426 required this.color,
427 this.locale,
428 this.localizationsDelegates,
429 this.localeListResolutionCallback,
430 this.localeResolutionCallback,
431 this.supportedLocales = const <Locale>[Locale('en', 'US')],
432 this.showPerformanceOverlay = false,
433 this.checkerboardRasterCacheImages = false,
434 this.checkerboardOffscreenLayers = false,
435 this.showSemanticsDebugger = false,
436 this.debugShowWidgetInspector = false,
437 this.debugShowCheckedModeBanner = true,
438 this.inspectorSelectButtonBuilder,
439 this.shortcuts,
440 this.actions,
441 this.restorationScopeId,
442 @Deprecated(
443 'Remove this parameter as it is now ignored. '
444 'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. '
445 'This feature was deprecated after v3.7.0-29.0.pre.'
446 )
447 this.useInheritedMediaQuery = false,
448 }) : assert((){
449 if (routerConfig != null) {
450 assert(
451 (routeInformationProvider ?? routeInformationParser ?? routerDelegate ?? backButtonDispatcher) == null,
452 'If the routerConfig is provided, all the other router delegates must not be provided',
453 );
454 return true;
455 }
456 assert(routerDelegate != null, 'Either one of routerDelegate or routerConfig must be provided');
457 assert(
458 routeInformationProvider == null || routeInformationParser != null,
459 'If routeInformationProvider is provided, routeInformationParser must also be provided',
460 );
461 return true;
462 }()),
463 assert(supportedLocales.isNotEmpty),
464 navigatorObservers = null,
465 navigatorKey = null,
466 onGenerateRoute = null,
467 pageRouteBuilder = null,
468 home = null,
469 onGenerateInitialRoutes = null,
470 onUnknownRoute = null,
471 routes = null,
472 initialRoute = null;
473
474 /// {@template flutter.widgets.widgetsApp.navigatorKey}
475 /// A key to use when building the [Navigator].
476 ///
477 /// If a [navigatorKey] is specified, the [Navigator] can be directly
478 /// manipulated without first obtaining it from a [BuildContext] via
479 /// [Navigator.of]: from the [navigatorKey], use the [GlobalKey.currentState]
480 /// getter.
481 ///
482 /// If this is changed, a new [Navigator] will be created, losing all the
483 /// application state in the process; in that case, the [navigatorObservers]
484 /// must also be changed, since the previous observers will be attached to the
485 /// previous navigator.
486 ///
487 /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
488 /// null, [navigatorKey] must also be null.
489 /// {@endtemplate}
490 final GlobalKey<NavigatorState>? navigatorKey;
491
492 /// {@template flutter.widgets.widgetsApp.onGenerateRoute}
493 /// The route generator callback used when the app is navigated to a
494 /// named route.
495 ///
496 /// If this returns null when building the routes to handle the specified
497 /// [initialRoute], then all the routes are discarded and
498 /// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
499 ///
500 /// During normal app operation, the [onGenerateRoute] callback will only be
501 /// applied to route names pushed by the application, and so should never
502 /// return null.
503 ///
504 /// This is used if [routes] does not contain the requested route.
505 ///
506 /// The [Navigator] is only built if routes are provided (either via [home],
507 /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
508 /// [builder] must not be null.
509 /// {@endtemplate}
510 ///
511 /// If this property is not set, either the [routes] or [home] properties must
512 /// be set, and the [pageRouteBuilder] must also be set so that the
513 /// default handler will know what routes and [PageRoute]s to build.
514 final RouteFactory? onGenerateRoute;
515
516 /// {@template flutter.widgets.widgetsApp.onGenerateInitialRoutes}
517 /// The routes generator callback used for generating initial routes if
518 /// [initialRoute] is provided.
519 ///
520 /// If this property is not set, the underlying
521 /// [Navigator.onGenerateInitialRoutes] will default to
522 /// [Navigator.defaultGenerateInitialRoutes].
523 /// {@endtemplate}
524 final InitialRouteListFactory? onGenerateInitialRoutes;
525
526 /// The [PageRoute] generator callback used when the app is navigated to a
527 /// named route.
528 ///
529 /// A [PageRoute] represents the page in a [Navigator], so that it can
530 /// correctly animate between pages, and to represent the "return value" of
531 /// a route (e.g. which button a user selected in a modal dialog).
532 ///
533 /// This callback can be used, for example, to specify that a [MaterialPageRoute]
534 /// or a [CupertinoPageRoute] should be used for building page transitions.
535 ///
536 /// The [PageRouteFactory] type is generic, meaning the provided function must
537 /// itself be generic. For example (with special emphasis on the `<T>` at the
538 /// start of the closure):
539 ///
540 /// ```dart
541 /// pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => PageRouteBuilder<T>(
542 /// settings: settings,
543 /// pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => builder(context),
544 /// ),
545 /// ```
546 final PageRouteFactory? pageRouteBuilder;
547
548 /// {@template flutter.widgets.widgetsApp.routeInformationParser}
549 /// A delegate to parse the route information from the
550 /// [routeInformationProvider] into a generic data type to be processed by
551 /// the [routerDelegate] at a later stage.
552 ///
553 /// This object will be used by the underlying [Router].
554 ///
555 /// The generic type `T` must match the generic type of the [routerDelegate].
556 ///
557 /// See also:
558 ///
559 /// * [Router.routeInformationParser], which receives this object when this
560 /// widget builds the [Router].
561 /// {@endtemplate}
562 final RouteInformationParser<Object>? routeInformationParser;
563
564 /// {@template flutter.widgets.widgetsApp.routerDelegate}
565 /// A delegate that configures a widget, typically a [Navigator], with
566 /// parsed result from the [routeInformationParser].
567 ///
568 /// This object will be used by the underlying [Router].
569 ///
570 /// The generic type `T` must match the generic type of the
571 /// [routeInformationParser].
572 ///
573 /// See also:
574 ///
575 /// * [Router.routerDelegate], which receives this object when this widget
576 /// builds the [Router].
577 /// {@endtemplate}
578 final RouterDelegate<Object>? routerDelegate;
579
580 /// {@template flutter.widgets.widgetsApp.backButtonDispatcher}
581 /// A delegate that decide whether to handle the Android back button intent.
582 ///
583 /// This object will be used by the underlying [Router].
584 ///
585 /// If this is not provided, the widgets app will create a
586 /// [RootBackButtonDispatcher] by default.
587 ///
588 /// See also:
589 ///
590 /// * [Router.backButtonDispatcher], which receives this object when this
591 /// widget builds the [Router].
592 /// {@endtemplate}
593 final BackButtonDispatcher? backButtonDispatcher;
594
595 /// {@template flutter.widgets.widgetsApp.routeInformationProvider}
596 /// A object that provides route information through the
597 /// [RouteInformationProvider.value] and notifies its listener when its value
598 /// changes.
599 ///
600 /// This object will be used by the underlying [Router].
601 ///
602 /// If this is not provided, the widgets app will create a
603 /// [PlatformRouteInformationProvider] with initial route name equal to the
604 /// [dart:ui.PlatformDispatcher.defaultRouteName] by default.
605 ///
606 /// See also:
607 ///
608 /// * [Router.routeInformationProvider], which receives this object when this
609 /// widget builds the [Router].
610 /// {@endtemplate}
611 final RouteInformationProvider? routeInformationProvider;
612
613 /// {@template flutter.widgets.widgetsApp.routerConfig}
614 /// An object to configure the underlying [Router].
615 ///
616 /// If the [routerConfig] is provided, the other router related delegates,
617 /// [routeInformationParser], [routeInformationProvider], [routerDelegate],
618 /// and [backButtonDispatcher], must all be null.
619 ///
620 /// See also:
621 ///
622 /// * [Router.withConfig], which receives this object when this
623 /// widget builds the [Router].
624 /// {@endtemplate}
625 final RouterConfig<Object>? routerConfig;
626
627 /// {@template flutter.widgets.widgetsApp.home}
628 /// The widget for the default route of the app ([Navigator.defaultRouteName],
629 /// which is `/`).
630 ///
631 /// This is the route that is displayed first when the application is started
632 /// normally, unless [initialRoute] is specified. It's also the route that's
633 /// displayed if the [initialRoute] can't be displayed.
634 ///
635 /// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code
636 /// that sets the [home] argument in the constructor, you can use a [Builder]
637 /// widget to get a [BuildContext].
638 ///
639 /// If [home] is specified, then [routes] must not include an entry for `/`,
640 /// as [home] takes its place.
641 ///
642 /// The [Navigator] is only built if routes are provided (either via [home],
643 /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
644 /// [builder] must not be null.
645 ///
646 /// The difference between using [home] and using [builder] is that the [home]
647 /// subtree is inserted into the application below a [Navigator] (and thus
648 /// below an [Overlay], which [Navigator] uses). With [home], therefore,
649 /// dialog boxes will work automatically, the [routes] table will be used, and
650 /// APIs such as [Navigator.push] and [Navigator.pop] will work as expected.
651 /// In contrast, the widget returned from [builder] is inserted _above_ the
652 /// app's [Navigator] (if any).
653 /// {@endtemplate}
654 ///
655 /// If this property is set, the [pageRouteBuilder] property must also be set
656 /// so that the default route handler will know what kind of [PageRoute]s to
657 /// build.
658 final Widget? home;
659
660 /// The application's top-level routing table.
661 ///
662 /// When a named route is pushed with [Navigator.pushNamed], the route name is
663 /// looked up in this map. If the name is present, the associated
664 /// [widgets.WidgetBuilder] is used to construct a [PageRoute] specified by
665 /// [pageRouteBuilder] to perform an appropriate transition, including [Hero]
666 /// animations, to the new route.
667 ///
668 /// {@template flutter.widgets.widgetsApp.routes}
669 /// If the app only has one page, then you can specify it using [home] instead.
670 ///
671 /// If [home] is specified, then it implies an entry in this table for the
672 /// [Navigator.defaultRouteName] route (`/`), and it is an error to
673 /// redundantly provide such a route in the [routes] table.
674 ///
675 /// If a route is requested that is not specified in this table (or by
676 /// [home]), then the [onGenerateRoute] callback is called to build the page
677 /// instead.
678 ///
679 /// The [Navigator] is only built if routes are provided (either via [home],
680 /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
681 /// [builder] must not be null.
682 /// {@endtemplate}
683 ///
684 /// If the routes map is not empty, the [pageRouteBuilder] property must be set
685 /// so that the default route handler will know what kind of [PageRoute]s to
686 /// build.
687 final Map<String, WidgetBuilder>? routes;
688
689 /// {@template flutter.widgets.widgetsApp.onUnknownRoute}
690 /// Called when [onGenerateRoute] fails to generate a route, except for the
691 /// [initialRoute].
692 ///
693 /// This callback is typically used for error handling. For example, this
694 /// callback might always generate a "not found" page that describes the route
695 /// that wasn't found.
696 ///
697 /// Unknown routes can arise either from errors in the app or from external
698 /// requests to push routes, such as from Android intents.
699 ///
700 /// The [Navigator] is only built if routes are provided (either via [home],
701 /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
702 /// [builder] must not be null.
703 /// {@endtemplate}
704 final RouteFactory? onUnknownRoute;
705
706 /// {@template flutter.widgets.widgetsApp.onNavigationNotification}
707 /// The callback to use when receiving a [NavigationNotification].
708 ///
709 /// By default this updates the engine with the navigation status.
710 /// {@endtemplate}
711 final NotificationListenerCallback<NavigationNotification>? onNavigationNotification;
712
713 /// {@template flutter.widgets.widgetsApp.initialRoute}
714 /// The name of the first route to show, if a [Navigator] is built.
715 ///
716 /// Defaults to [dart:ui.PlatformDispatcher.defaultRouteName], which may be
717 /// overridden by the code that launched the application.
718 ///
719 /// If the route name starts with a slash, then it is treated as a "deep link",
720 /// and before this route is pushed, the routes leading to this one are pushed
721 /// also. For example, if the route was `/a/b/c`, then the app would start
722 /// with the four routes `/`, `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
723 /// Even if the route was just `/a`, the app would start with `/` and `/a`
724 /// loaded. You can use the [onGenerateInitialRoutes] property to override
725 /// this behavior.
726 ///
727 /// Intermediate routes aren't required to exist. In the example above, `/a`
728 /// and `/a/b` could be skipped if they have no matching route. But `/a/b/c` is
729 /// required to have a route, else [initialRoute] is ignored and
730 /// [Navigator.defaultRouteName] is used instead (`/`). This can happen if the
731 /// app is started with an intent that specifies a non-existent route.
732 ///
733 /// The [Navigator] is only built if routes are provided (either via [home],
734 /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
735 /// [initialRoute] must be null and [builder] must not be null.
736 ///
737 /// Changing the [initialRoute] will have no effect, as it only controls the
738 /// _initial_ route. To change the route while the application is running, use
739 /// the [Navigator] or [Router] APIs.
740 ///
741 /// See also:
742 ///
743 /// * [Navigator.initialRoute], which is used to implement this property.
744 /// * [Navigator.push], for pushing additional routes.
745 /// * [Navigator.pop], for removing a route from the stack.
746 ///
747 /// {@endtemplate}
748 final String? initialRoute;
749
750 /// {@template flutter.widgets.widgetsApp.navigatorObservers}
751 /// The list of observers for the [Navigator] created for this app.
752 ///
753 /// This list must be replaced by a list of newly-created observers if the
754 /// [navigatorKey] is changed.
755 ///
756 /// The [Navigator] is only built if routes are provided (either via [home],
757 /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
758 /// [navigatorObservers] must be the empty list and [builder] must not be null.
759 /// {@endtemplate}
760 final List<NavigatorObserver>? navigatorObservers;
761
762 /// {@template flutter.widgets.widgetsApp.builder}
763 /// A builder for inserting widgets above the [Navigator] or - when the
764 /// [WidgetsApp.router] constructor is used - above the [Router] but below the
765 /// other widgets created by the [WidgetsApp] widget, or for replacing the
766 /// [Navigator]/[Router] entirely.
767 ///
768 /// For example, from the [BuildContext] passed to this method, the
769 /// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc,
770 /// are all available. They can also be overridden in a way that impacts all
771 /// the routes in the [Navigator] or [Router].
772 ///
773 /// This is rarely useful, but can be used in applications that wish to
774 /// override those defaults, e.g. to force the application into right-to-left
775 /// mode despite being in English, or to override the [MediaQuery] metrics
776 /// (e.g. to leave a gap for advertisements shown by a plugin from OEM code).
777 ///
778 /// For specifically overriding the [title] with a value based on the
779 /// [Localizations], consider [onGenerateTitle] instead.
780 ///
781 /// The [builder] callback is passed two arguments, the [BuildContext] (as
782 /// `context`) and a [Navigator] or [Router] widget (as `child`).
783 ///
784 /// If no routes are provided to the regular [WidgetsApp] constructor using
785 /// [home], [routes], [onGenerateRoute], or [onUnknownRoute], the `child` will
786 /// be null, and it is the responsibility of the [builder] to provide the
787 /// application's routing machinery.
788 ///
789 /// If routes _are_ provided to the regular [WidgetsApp] constructor using one
790 /// or more of those properties or if the [WidgetsApp.router] constructor is
791 /// used, then `child` is not null, and the returned value should include the
792 /// `child` in the widget subtree; if it does not, then the application will
793 /// have no [Navigator] or [Router] and the routing related properties (i.e.
794 /// [navigatorKey], [home], [routes], [onGenerateRoute], [onUnknownRoute],
795 /// [initialRoute], [navigatorObservers], [routeInformationProvider],
796 /// [backButtonDispatcher], [routerDelegate], and [routeInformationParser])
797 /// are ignored.
798 ///
799 /// If [builder] is null, it is as if a builder was specified that returned
800 /// the `child` directly. If it is null, routes must be provided using one of
801 /// the other properties listed above.
802 ///
803 /// Unless a [Navigator] is provided, either implicitly from [builder] being
804 /// null, or by a [builder] including its `child` argument, or by a [builder]
805 /// explicitly providing a [Navigator] of its own, or by the [routerDelegate]
806 /// building one, widgets and APIs such as [Hero], [Navigator.push] and
807 /// [Navigator.pop], will not function.
808 /// {@endtemplate}
809 final TransitionBuilder? builder;
810
811 /// {@template flutter.widgets.widgetsApp.title}
812 /// A one-line description used by the device to identify the app for the user.
813 ///
814 /// On Android the titles appear above the task manager's app snapshots which are
815 /// displayed when the user presses the "recent apps" button. On iOS this
816 /// value cannot be used. `CFBundleDisplayName` from the app's `Info.plist` is
817 /// referred to instead whenever present, `CFBundleName` otherwise.
818 /// On the web it is used as the page title, which shows up in the browser's list of open tabs.
819 ///
820 /// To provide a localized title instead, use [onGenerateTitle].
821 /// {@endtemplate}
822 final String title;
823
824 /// {@template flutter.widgets.widgetsApp.onGenerateTitle}
825 /// If non-null this callback function is called to produce the app's
826 /// title string, otherwise [title] is used.
827 ///
828 /// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s
829 /// [Localizations] widget so that this callback can be used to produce a
830 /// localized title.
831 ///
832 /// This callback function must not return null.
833 ///
834 /// The [onGenerateTitle] callback is called each time the [WidgetsApp]
835 /// rebuilds.
836 /// {@endtemplate}
837 final GenerateAppTitle? onGenerateTitle;
838
839 /// The default text style for [Text] in the application.
840 final TextStyle? textStyle;
841
842 /// {@template flutter.widgets.widgetsApp.color}
843 /// The primary color to use for the application in the operating system
844 /// interface.
845 ///
846 /// For example, on Android this is the color used for the application in the
847 /// application switcher.
848 /// {@endtemplate}
849 final Color color;
850
851 /// {@template flutter.widgets.widgetsApp.locale}
852 /// The initial locale for this app's [Localizations] widget is based
853 /// on this value.
854 ///
855 /// If the 'locale' is null then the system's locale value is used.
856 ///
857 /// The value of [Localizations.locale] will equal this locale if
858 /// it matches one of the [supportedLocales]. Otherwise it will be
859 /// the first element of [supportedLocales].
860 /// {@endtemplate}
861 ///
862 /// See also:
863 ///
864 /// * [localeResolutionCallback], which can override the default
865 /// [supportedLocales] matching algorithm.
866 /// * [localizationsDelegates], which collectively define all of the localized
867 /// resources used by this app.
868 final Locale? locale;
869
870 /// {@template flutter.widgets.widgetsApp.localizationsDelegates}
871 /// The delegates for this app's [Localizations] widget.
872 ///
873 /// The delegates collectively define all of the localized resources
874 /// for this application's [Localizations] widget.
875 /// {@endtemplate}
876 final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
877
878 /// {@template flutter.widgets.widgetsApp.localeListResolutionCallback}
879 /// This callback is responsible for choosing the app's locale
880 /// when the app is started, and when the user changes the
881 /// device's locale.
882 ///
883 /// When a [localeListResolutionCallback] is provided, Flutter will first
884 /// attempt to resolve the locale with the provided
885 /// [localeListResolutionCallback]. If the callback or result is null, it will
886 /// fallback to trying the [localeResolutionCallback]. If both
887 /// [localeResolutionCallback] and [localeListResolutionCallback] are left
888 /// null or fail to resolve (return null), basic fallback algorithm will
889 /// be used.
890 ///
891 /// The priority of each available fallback is:
892 ///
893 /// 1. [localeListResolutionCallback] is attempted.
894 /// 2. [localeResolutionCallback] is attempted.
895 /// 3. Flutter's basic resolution algorithm, as described in
896 /// [supportedLocales], is attempted last.
897 ///
898 /// Properly localized projects should provide a more advanced algorithm than
899 /// the basic method from [supportedLocales], as it does not implement a
900 /// complete algorithm (such as the one defined in
901 /// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching))
902 /// and is optimized for speed at the detriment of some uncommon edge-cases.
903 /// {@endtemplate}
904 ///
905 /// This callback considers the entire list of preferred locales.
906 ///
907 /// This algorithm should be able to handle a null or empty list of preferred locales,
908 /// which indicates Flutter has not yet received locale information from the platform.
909 ///
910 /// See also:
911 ///
912 /// * [MaterialApp.localeListResolutionCallback], which sets the callback of the
913 /// [WidgetsApp] it creates.
914 /// * [basicLocaleListResolution], the default locale resolution algorithm.
915 final LocaleListResolutionCallback? localeListResolutionCallback;
916
917 /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
918 ///
919 /// This callback considers only the default locale, which is the first locale
920 /// in the preferred locales list. It is preferred to set [localeListResolutionCallback]
921 /// over [localeResolutionCallback] as it provides the full preferred locales list.
922 ///
923 /// This algorithm should be able to handle a null locale, which indicates
924 /// Flutter has not yet received locale information from the platform.
925 ///
926 /// See also:
927 ///
928 /// * [MaterialApp.localeResolutionCallback], which sets the callback of the
929 /// [WidgetsApp] it creates.
930 /// * [basicLocaleListResolution], the default locale resolution algorithm.
931 final LocaleResolutionCallback? localeResolutionCallback;
932
933 /// {@template flutter.widgets.widgetsApp.supportedLocales}
934 /// The list of locales that this app has been localized for.
935 ///
936 /// By default only the American English locale is supported. Apps should
937 /// configure this list to match the locales they support.
938 ///
939 /// This list must not null. Its default value is just
940 /// `[const Locale('en', 'US')]`.
941 ///
942 /// The order of the list matters. The default locale resolution algorithm,
943 /// [basicLocaleListResolution], attempts to match by the following priority:
944 ///
945 /// 1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode]
946 /// 2. [Locale.languageCode] and [Locale.scriptCode] only
947 /// 3. [Locale.languageCode] and [Locale.countryCode] only
948 /// 4. [Locale.languageCode] only
949 /// 5. [Locale.countryCode] only when all preferred locales fail to match
950 /// 6. Returns the first element of [supportedLocales] as a fallback
951 ///
952 /// When more than one supported locale matches one of these criteria, only
953 /// the first matching locale is returned.
954 ///
955 /// The default locale resolution algorithm can be overridden by providing a
956 /// value for [localeListResolutionCallback]. The provided
957 /// [basicLocaleListResolution] is optimized for speed and does not implement
958 /// a full algorithm (such as the one defined in
959 /// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching)) that
960 /// takes distances between languages into account.
961 ///
962 /// When supporting languages with more than one script, it is recommended
963 /// to specify the [Locale.scriptCode] explicitly. Locales may also be defined without
964 /// [Locale.countryCode] to specify a generic fallback for a particular script.
965 ///
966 /// A fully supported language with multiple scripts should define a generic language-only
967 /// locale (e.g. 'zh'), language+script only locales (e.g. 'zh_Hans' and 'zh_Hant'),
968 /// and any language+script+country locales (e.g. 'zh_Hans_CN'). Fully defining all of
969 /// these locales as supported is not strictly required but allows for proper locale resolution in
970 /// the most number of cases. These locales can be specified with the [Locale.fromSubtags]
971 /// constructor:
972 ///
973 /// ```dart
974 /// // Full Chinese support for CN, TW, and HK
975 /// supportedLocales: <Locale>[
976 /// const Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
977 /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans'
978 /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant'
979 /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'), // 'zh_Hans_CN'
980 /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'), // 'zh_Hant_TW'
981 /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // 'zh_Hant_HK'
982 /// ],
983 /// ```
984 ///
985 /// Omitting some these fallbacks may result in improperly resolved
986 /// edge-cases, for example, a simplified Chinese user in Taiwan ('zh_Hans_TW')
987 /// may resolve to traditional Chinese if 'zh_Hans' and 'zh_Hans_CN' are
988 /// omitted.
989 /// {@endtemplate}
990 ///
991 /// See also:
992 ///
993 /// * [MaterialApp.supportedLocales], which sets the `supportedLocales`
994 /// of the [WidgetsApp] it creates.
995 /// * [localeResolutionCallback], an app callback that resolves the app's locale
996 /// when the device's locale changes.
997 /// * [localizationsDelegates], which collectively define all of the localized
998 /// resources used by this app.
999 /// * [basicLocaleListResolution], the default locale resolution algorithm.
1000 final Iterable<Locale> supportedLocales;
1001
1002 /// Turns on a performance overlay.
1003 ///
1004 /// See also:
1005 ///
1006 /// * <https://flutter.dev/debugging/#performance-overlay>
1007 final bool showPerformanceOverlay;
1008
1009 /// Checkerboards raster cache images.
1010 ///
1011 /// See [PerformanceOverlay.checkerboardRasterCacheImages].
1012 final bool checkerboardRasterCacheImages;
1013
1014 /// Checkerboards layers rendered to offscreen bitmaps.
1015 ///
1016 /// See [PerformanceOverlay.checkerboardOffscreenLayers].
1017 final bool checkerboardOffscreenLayers;
1018
1019 /// Turns on an overlay that shows the accessibility information
1020 /// reported by the framework.
1021 final bool showSemanticsDebugger;
1022
1023 /// Turns on an overlay that enables inspecting the widget tree.
1024 ///
1025 /// The inspector is only available in debug mode as it depends on
1026 /// [RenderObject.debugDescribeChildren] which should not be called outside of
1027 /// debug mode.
1028 final bool debugShowWidgetInspector;
1029
1030 /// Builds the widget the [WidgetInspector] uses to switch between view and
1031 /// inspect modes.
1032 ///
1033 /// This lets [MaterialApp] to use a Material Design button to toggle the
1034 /// inspector select mode without requiring [WidgetInspector] to depend on the
1035 /// material package.
1036 final InspectorSelectButtonBuilder? inspectorSelectButtonBuilder;
1037
1038 /// {@template flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
1039 /// Turns on a little "DEBUG" banner in debug mode to indicate
1040 /// that the app is in debug mode. This is on by default (in
1041 /// debug mode), to turn it off, set the constructor argument to
1042 /// false. In release mode this has no effect.
1043 ///
1044 /// To get this banner in your application if you're not using
1045 /// WidgetsApp, include a [CheckedModeBanner] widget in your app.
1046 ///
1047 /// This banner is intended to deter people from complaining that your
1048 /// app is slow when it's in debug mode. In debug mode, Flutter
1049 /// enables a large number of expensive diagnostics to aid in
1050 /// development, and so performance in debug mode is not
1051 /// representative of what will happen in release mode.
1052 /// {@endtemplate}
1053 final bool debugShowCheckedModeBanner;
1054
1055 /// {@template flutter.widgets.widgetsApp.shortcuts}
1056 /// The default map of keyboard shortcuts to intents for the application.
1057 ///
1058 /// By default, this is set to [WidgetsApp.defaultShortcuts].
1059 ///
1060 /// Passing this will not replace [DefaultTextEditingShortcuts]. These can be
1061 /// overridden by using a [Shortcuts] widget lower in the widget tree.
1062 /// {@endtemplate}
1063 ///
1064 /// {@tool snippet}
1065 /// This example shows how to add a single shortcut for
1066 /// [LogicalKeyboardKey.select] to the default shortcuts without needing to
1067 /// add your own [Shortcuts] widget.
1068 ///
1069 /// Alternatively, you could insert a [Shortcuts] widget with just the mapping
1070 /// you want to add between the [WidgetsApp] and its child and get the same
1071 /// effect.
1072 ///
1073 /// ```dart
1074 /// Widget build(BuildContext context) {
1075 /// return WidgetsApp(
1076 /// shortcuts: <ShortcutActivator, Intent>{
1077 /// ... WidgetsApp.defaultShortcuts,
1078 /// const SingleActivator(LogicalKeyboardKey.select): const ActivateIntent(),
1079 /// },
1080 /// color: const Color(0xFFFF0000),
1081 /// builder: (BuildContext context, Widget? child) {
1082 /// return const Placeholder();
1083 /// },
1084 /// );
1085 /// }
1086 /// ```
1087 /// {@end-tool}
1088 ///
1089 /// {@template flutter.widgets.widgetsApp.shortcuts.seeAlso}
1090 /// See also:
1091 ///
1092 /// * [SingleActivator], which defines shortcut key combination of a single
1093 /// key and modifiers, such as "Delete" or "Control+C".
1094 /// * The [Shortcuts] widget, which defines a keyboard mapping.
1095 /// * The [Actions] widget, which defines the mapping from intent to action.
1096 /// * The [Intent] and [Action] classes, which allow definition of new
1097 /// actions.
1098 /// {@endtemplate}
1099 final Map<ShortcutActivator, Intent>? shortcuts;
1100
1101 /// {@template flutter.widgets.widgetsApp.actions}
1102 /// The default map of intent keys to actions for the application.
1103 ///
1104 /// By default, this is the output of [WidgetsApp.defaultActions], called with
1105 /// [defaultTargetPlatform]. Specifying [actions] for an app overrides the
1106 /// default, so if you wish to modify the default [actions], you can call
1107 /// [WidgetsApp.defaultActions] and modify the resulting map, passing it as
1108 /// the [actions] for this app. You may also add to the bindings, or override
1109 /// specific bindings for a widget subtree, by adding your own [Actions]
1110 /// widget.
1111 /// {@endtemplate}
1112 ///
1113 /// {@tool snippet}
1114 /// This example shows how to add a single action handling an
1115 /// [ActivateAction] to the default actions without needing to
1116 /// add your own [Actions] widget.
1117 ///
1118 /// Alternatively, you could insert a [Actions] widget with just the mapping
1119 /// you want to add between the [WidgetsApp] and its child and get the same
1120 /// effect.
1121 ///
1122 /// ```dart
1123 /// Widget build(BuildContext context) {
1124 /// return WidgetsApp(
1125 /// actions: <Type, Action<Intent>>{
1126 /// ... WidgetsApp.defaultActions,
1127 /// ActivateAction: CallbackAction<Intent>(
1128 /// onInvoke: (Intent intent) {
1129 /// // Do something here...
1130 /// return null;
1131 /// },
1132 /// ),
1133 /// },
1134 /// color: const Color(0xFFFF0000),
1135 /// builder: (BuildContext context, Widget? child) {
1136 /// return const Placeholder();
1137 /// },
1138 /// );
1139 /// }
1140 /// ```
1141 /// {@end-tool}
1142 ///
1143 /// {@template flutter.widgets.widgetsApp.actions.seeAlso}
1144 /// See also:
1145 ///
1146 /// * The [shortcuts] parameter, which defines the default set of shortcuts
1147 /// for the application.
1148 /// * The [Shortcuts] widget, which defines a keyboard mapping.
1149 /// * The [Actions] widget, which defines the mapping from intent to action.
1150 /// * The [Intent] and [Action] classes, which allow definition of new
1151 /// actions.
1152 /// {@endtemplate}
1153 final Map<Type, Action<Intent>>? actions;
1154
1155 /// {@template flutter.widgets.widgetsApp.restorationScopeId}
1156 /// The identifier to use for state restoration of this app.
1157 ///
1158 /// Providing a restoration ID inserts a [RootRestorationScope] into the
1159 /// widget hierarchy, which enables state restoration for descendant widgets.
1160 ///
1161 /// Providing a restoration ID also enables the [Navigator] or [Router] built
1162 /// by the [WidgetsApp] to restore its state (i.e. to restore the history
1163 /// stack of active [Route]s). See the documentation on [Navigator] for more
1164 /// details around state restoration of [Route]s.
1165 ///
1166 /// See also:
1167 ///
1168 /// * [RestorationManager], which explains how state restoration works in
1169 /// Flutter.
1170 /// {@endtemplate}
1171 final String? restorationScopeId;
1172
1173 /// {@template flutter.widgets.widgetsApp.useInheritedMediaQuery}
1174 /// Deprecated. This setting is now ignored.
1175 ///
1176 /// The widget never introduces its own [MediaQuery]; the [View] widget takes
1177 /// care of that.
1178 /// {@endtemplate}
1179 @Deprecated(
1180 'This setting is now ignored. '
1181 'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. '
1182 'This feature was deprecated after v3.7.0-29.0.pre.'
1183 )
1184 final bool useInheritedMediaQuery;
1185
1186 /// If true, forces the performance overlay to be visible in all instances.
1187 ///
1188 /// Used by the `showPerformanceOverlay` VM service extension.
1189 static bool showPerformanceOverlayOverride = false;
1190
1191 /// If true, forces the widget inspector to be visible.
1192 ///
1193 /// Overrides the `debugShowWidgetInspector` value set in [WidgetsApp].
1194 ///
1195 /// Used by the `debugShowWidgetInspector` debugging extension.
1196 ///
1197 /// The inspector allows the selection of a location on your device or emulator
1198 /// and view what widgets and render objects associated with it. An outline of
1199 /// the selected widget and some summary information is shown on device and
1200 /// more detailed information is shown in the IDE or DevTools.
1201 static bool get debugShowWidgetInspectorOverride {
1202 return _debugShowWidgetInspectorOverrideNotifier.value;
1203 }
1204 static set debugShowWidgetInspectorOverride(bool value) {
1205 _debugShowWidgetInspectorOverrideNotifier.value = value;
1206 }
1207
1208 static final ValueNotifier<bool> _debugShowWidgetInspectorOverrideNotifier = ValueNotifier<bool>(false);
1209
1210 /// If false, prevents the debug banner from being visible.
1211 ///
1212 /// Used by the `debugAllowBanner` VM service extension.
1213 ///
1214 /// This is how `flutter run` turns off the banner when you take a screen shot
1215 /// with "s".
1216 static bool debugAllowBannerOverride = true;
1217
1218 static const Map<ShortcutActivator, Intent> _defaultShortcuts = <ShortcutActivator, Intent>{
1219 // Activation
1220 SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(),
1221 SingleActivator(LogicalKeyboardKey.numpadEnter): ActivateIntent(),
1222 SingleActivator(LogicalKeyboardKey.space): ActivateIntent(),
1223 SingleActivator(LogicalKeyboardKey.gameButtonA): ActivateIntent(),
1224 SingleActivator(LogicalKeyboardKey.select): ActivateIntent(),
1225
1226 // Dismissal
1227 SingleActivator(LogicalKeyboardKey.escape): DismissIntent(),
1228
1229 // Keyboard traversal.
1230 SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(),
1231 SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(),
1232 SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left),
1233 SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right),
1234 SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down),
1235 SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up),
1236
1237 // Scrolling
1238 SingleActivator(LogicalKeyboardKey.arrowUp, control: true): ScrollIntent(direction: AxisDirection.up),
1239 SingleActivator(LogicalKeyboardKey.arrowDown, control: true): ScrollIntent(direction: AxisDirection.down),
1240 SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): ScrollIntent(direction: AxisDirection.left),
1241 SingleActivator(LogicalKeyboardKey.arrowRight, control: true): ScrollIntent(direction: AxisDirection.right),
1242 SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page),
1243 SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
1244 };
1245
1246 // Default shortcuts for the web platform.
1247 static const Map<ShortcutActivator, Intent> _defaultWebShortcuts = <ShortcutActivator, Intent>{
1248 // Activation
1249 SingleActivator(LogicalKeyboardKey.space): PrioritizedIntents(
1250 orderedIntents: <Intent>[
1251 ActivateIntent(),
1252 ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
1253 ],
1254 ),
1255 // On the web, enter activates buttons, but not other controls.
1256 SingleActivator(LogicalKeyboardKey.enter): ButtonActivateIntent(),
1257 SingleActivator(LogicalKeyboardKey.numpadEnter): ButtonActivateIntent(),
1258
1259 // Dismissal
1260 SingleActivator(LogicalKeyboardKey.escape): DismissIntent(),
1261
1262 // Keyboard traversal.
1263 SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(),
1264 SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(),
1265
1266 // Scrolling
1267 SingleActivator(LogicalKeyboardKey.arrowUp): ScrollIntent(direction: AxisDirection.up),
1268 SingleActivator(LogicalKeyboardKey.arrowDown): ScrollIntent(direction: AxisDirection.down),
1269 SingleActivator(LogicalKeyboardKey.arrowLeft): ScrollIntent(direction: AxisDirection.left),
1270 SingleActivator(LogicalKeyboardKey.arrowRight): ScrollIntent(direction: AxisDirection.right),
1271 SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page),
1272 SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
1273 };
1274
1275 // Default shortcuts for the macOS platform.
1276 static const Map<ShortcutActivator, Intent> _defaultAppleOsShortcuts = <ShortcutActivator, Intent>{
1277 // Activation
1278 SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(),
1279 SingleActivator(LogicalKeyboardKey.numpadEnter): ActivateIntent(),
1280 SingleActivator(LogicalKeyboardKey.space): ActivateIntent(),
1281
1282 // Dismissal
1283 SingleActivator(LogicalKeyboardKey.escape): DismissIntent(),
1284
1285 // Keyboard traversal
1286 SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(),
1287 SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(),
1288 SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left),
1289 SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right),
1290 SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down),
1291 SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up),
1292
1293 // Scrolling
1294 SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): ScrollIntent(direction: AxisDirection.up),
1295 SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): ScrollIntent(direction: AxisDirection.down),
1296 SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): ScrollIntent(direction: AxisDirection.left),
1297 SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): ScrollIntent(direction: AxisDirection.right),
1298 SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page),
1299 SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
1300 };
1301
1302 /// Generates the default shortcut key bindings based on the
1303 /// [defaultTargetPlatform].
1304 ///
1305 /// Used by [WidgetsApp] to assign a default value to [WidgetsApp.shortcuts].
1306 static Map<ShortcutActivator, Intent> get defaultShortcuts {
1307 if (kIsWeb) {
1308 return _defaultWebShortcuts;
1309 }
1310
1311 switch (defaultTargetPlatform) {
1312 case TargetPlatform.android:
1313 case TargetPlatform.fuchsia:
1314 case TargetPlatform.linux:
1315 case TargetPlatform.windows:
1316 return _defaultShortcuts;
1317 case TargetPlatform.iOS:
1318 case TargetPlatform.macOS:
1319 return _defaultAppleOsShortcuts;
1320 }
1321 }
1322
1323 /// The default value of [WidgetsApp.actions].
1324 static Map<Type, Action<Intent>> defaultActions = <Type, Action<Intent>>{
1325 DoNothingIntent: DoNothingAction(),
1326 DoNothingAndStopPropagationIntent: DoNothingAction(consumesKey: false),
1327 RequestFocusIntent: RequestFocusAction(),
1328 NextFocusIntent: NextFocusAction(),
1329 PreviousFocusIntent: PreviousFocusAction(),
1330 DirectionalFocusIntent: DirectionalFocusAction(),
1331 ScrollIntent: ScrollAction(),
1332 PrioritizedIntents: PrioritizedAction(),
1333 VoidCallbackIntent: VoidCallbackAction(),
1334 };
1335
1336 @override
1337 State<WidgetsApp> createState() => _WidgetsAppState();
1338}
1339
1340class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
1341 // STATE LIFECYCLE
1342
1343 // If window.defaultRouteName isn't '/', we should assume it was set
1344 // intentionally via `setInitialRoute`, and should override whatever is in
1345 // [widget.initialRoute].
1346 String get _initialRouteName => WidgetsBinding.instance.platformDispatcher.defaultRouteName != Navigator.defaultRouteName
1347 ? WidgetsBinding.instance.platformDispatcher.defaultRouteName
1348 : widget.initialRoute ?? WidgetsBinding.instance.platformDispatcher.defaultRouteName;
1349
1350 AppLifecycleState? _appLifecycleState;
1351
1352 /// The default value for [onNavigationNotification].
1353 ///
1354 /// Does nothing and stops bubbling if the app is detached. Otherwise, updates
1355 /// the platform with [NavigationNotification.canHandlePop] and stops
1356 /// bubbling.
1357 bool _defaultOnNavigationNotification(NavigationNotification notification) {
1358 switch (_appLifecycleState) {
1359 case null:
1360 case AppLifecycleState.detached:
1361 case AppLifecycleState.inactive:
1362 // Avoid updating the engine when the app isn't ready.
1363 return true;
1364 case AppLifecycleState.resumed:
1365 case AppLifecycleState.hidden:
1366 case AppLifecycleState.paused:
1367 SystemNavigator.setFrameworkHandlesBack(notification.canHandlePop);
1368 return true;
1369 }
1370 }
1371
1372 @override
1373 void didChangeAppLifecycleState(AppLifecycleState state) {
1374 _appLifecycleState = state;
1375 super.didChangeAppLifecycleState(state);
1376 }
1377
1378 @override
1379 void initState() {
1380 super.initState();
1381 _updateRouting();
1382 _locale = _resolveLocales(WidgetsBinding.instance.platformDispatcher.locales, widget.supportedLocales);
1383 WidgetsBinding.instance.addObserver(this);
1384 _appLifecycleState = WidgetsBinding.instance.lifecycleState;
1385 }
1386
1387 @override
1388 void didUpdateWidget(WidgetsApp oldWidget) {
1389 super.didUpdateWidget(oldWidget);
1390 _updateRouting(oldWidget: oldWidget);
1391 }
1392
1393 @override
1394 void dispose() {
1395 WidgetsBinding.instance.removeObserver(this);
1396 _defaultRouteInformationProvider?.dispose();
1397 super.dispose();
1398 }
1399
1400 void _clearRouterResource() {
1401 _defaultRouteInformationProvider?.dispose();
1402 _defaultRouteInformationProvider = null;
1403 _defaultBackButtonDispatcher = null;
1404 }
1405
1406 void _clearNavigatorResource() {
1407 _navigator = null;
1408 }
1409
1410 void _updateRouting({WidgetsApp? oldWidget}) {
1411 if (_usesRouterWithDelegates) {
1412 assert(!_usesNavigator && !_usesRouterWithConfig);
1413 _clearNavigatorResource();
1414 if (widget.routeInformationProvider == null && widget.routeInformationParser != null) {
1415 _defaultRouteInformationProvider ??= PlatformRouteInformationProvider(
1416 initialRouteInformation: RouteInformation(
1417 uri: Uri.parse(_initialRouteName),
1418 ),
1419 );
1420 } else {
1421 _defaultRouteInformationProvider?.dispose();
1422 _defaultRouteInformationProvider = null;
1423 }
1424 if (widget.backButtonDispatcher == null) {
1425 _defaultBackButtonDispatcher ??= RootBackButtonDispatcher();
1426 }
1427
1428 } else if (_usesNavigator) {
1429 assert(!_usesRouterWithDelegates && !_usesRouterWithConfig);
1430 _clearRouterResource();
1431 if (_navigator == null || widget.navigatorKey != oldWidget!.navigatorKey) {
1432 _navigator = widget.navigatorKey ?? GlobalObjectKey<NavigatorState>(this);
1433 }
1434 assert(_navigator != null);
1435 } else {
1436 assert(widget.builder != null || _usesRouterWithConfig);
1437 assert(!_usesRouterWithDelegates && !_usesNavigator);
1438 _clearRouterResource();
1439 _clearNavigatorResource();
1440 }
1441 // If we use a navigator, we have a navigator key.
1442 assert(_usesNavigator == (_navigator != null));
1443 }
1444
1445 bool get _usesRouterWithDelegates => widget.routerDelegate != null;
1446 bool get _usesRouterWithConfig => widget.routerConfig != null;
1447 bool get _usesNavigator => widget.home != null
1448 || (widget.routes?.isNotEmpty ?? false)
1449 || widget.onGenerateRoute != null
1450 || widget.onUnknownRoute != null;
1451
1452 // ROUTER
1453
1454 RouteInformationProvider? get _effectiveRouteInformationProvider => widget.routeInformationProvider ?? _defaultRouteInformationProvider;
1455 PlatformRouteInformationProvider? _defaultRouteInformationProvider;
1456 BackButtonDispatcher get _effectiveBackButtonDispatcher => widget.backButtonDispatcher ?? _defaultBackButtonDispatcher!;
1457 RootBackButtonDispatcher? _defaultBackButtonDispatcher;
1458
1459 // NAVIGATOR
1460
1461 GlobalKey<NavigatorState>? _navigator;
1462
1463 Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
1464 final String? name = settings.name;
1465 final WidgetBuilder? pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null
1466 ? (BuildContext context) => widget.home!
1467 : widget.routes![name];
1468
1469 if (pageContentBuilder != null) {
1470 assert(
1471 widget.pageRouteBuilder != null,
1472 'The default onGenerateRoute handler for WidgetsApp must have a '
1473 'pageRouteBuilder set if the home or routes properties are set.',
1474 );
1475 final Route<dynamic> route = widget.pageRouteBuilder!<dynamic>(
1476 settings,
1477 pageContentBuilder,
1478 );
1479 return route;
1480 }
1481 if (widget.onGenerateRoute != null) {
1482 return widget.onGenerateRoute!(settings);
1483 }
1484 return null;
1485 }
1486
1487 Route<dynamic> _onUnknownRoute(RouteSettings settings) {
1488 assert(() {
1489 if (widget.onUnknownRoute == null) {
1490 throw FlutterError(
1491 'Could not find a generator for route $settings in the $runtimeType.\n'
1492 'Make sure your root app widget has provided a way to generate \n'
1493 'this route.\n'
1494 'Generators for routes are searched for in the following order:\n'
1495 ' 1. For the "/" route, the "home" property, if non-null, is used.\n'
1496 ' 2. Otherwise, the "routes" table is used, if it has an entry for '
1497 'the route.\n'
1498 ' 3. Otherwise, onGenerateRoute is called. It should return a '
1499 'non-null value for any valid route not handled by "home" and "routes".\n'
1500 ' 4. Finally if all else fails onUnknownRoute is called.\n'
1501 'Unfortunately, onUnknownRoute was not set.',
1502 );
1503 }
1504 return true;
1505 }());
1506 final Route<dynamic>? result = widget.onUnknownRoute!(settings);
1507 assert(() {
1508 if (result == null) {
1509 throw FlutterError(
1510 'The onUnknownRoute callback returned null.\n'
1511 'When the $runtimeType requested the route $settings from its '
1512 'onUnknownRoute callback, the callback returned null. Such callbacks '
1513 'must never return null.',
1514 );
1515 }
1516 return true;
1517 }());
1518 return result!;
1519 }
1520
1521 // On Android: the user has pressed the back button.
1522 @override
1523 Future<bool> didPopRoute() async {
1524 assert(mounted);
1525 // The back button dispatcher should handle the pop route if we use a
1526 // router.
1527 if (_usesRouterWithDelegates) {
1528 return false;
1529 }
1530
1531 final NavigatorState? navigator = _navigator?.currentState;
1532 if (navigator == null) {
1533 return false;
1534 }
1535 return navigator.maybePop();
1536 }
1537
1538 @override
1539 Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
1540 assert(mounted);
1541 // The route name provider should handle the push route if we uses a
1542 // router.
1543 if (_usesRouterWithDelegates) {
1544 return false;
1545 }
1546
1547 final NavigatorState? navigator = _navigator?.currentState;
1548 if (navigator == null) {
1549 return false;
1550 }
1551 final Uri uri = routeInformation.uri;
1552 navigator.pushNamed(
1553 Uri.decodeComponent(
1554 Uri(
1555 path: uri.path.isEmpty ? '/' : uri.path,
1556 queryParameters: uri.queryParametersAll.isEmpty ? null : uri.queryParametersAll,
1557 fragment: uri.fragment.isEmpty ? null : uri.fragment,
1558 ).toString(),
1559 ),
1560 );
1561 return true;
1562 }
1563
1564 // LOCALIZATION
1565
1566 /// This is the resolved locale, and is one of the supportedLocales.
1567 Locale? _locale;
1568
1569 Locale _resolveLocales(List<Locale>? preferredLocales, Iterable<Locale> supportedLocales) {
1570 // Attempt to use localeListResolutionCallback.
1571 if (widget.localeListResolutionCallback != null) {
1572 final Locale? locale = widget.localeListResolutionCallback!(preferredLocales, widget.supportedLocales);
1573 if (locale != null) {
1574 return locale;
1575 }
1576 }
1577 // localeListResolutionCallback failed, falling back to localeResolutionCallback.
1578 if (widget.localeResolutionCallback != null) {
1579 final Locale? locale = widget.localeResolutionCallback!(
1580 preferredLocales != null && preferredLocales.isNotEmpty ? preferredLocales.first : null,
1581 widget.supportedLocales,
1582 );
1583 if (locale != null) {
1584 return locale;
1585 }
1586 }
1587 // Both callbacks failed, falling back to default algorithm.
1588 return basicLocaleListResolution(preferredLocales, supportedLocales);
1589 }
1590
1591 @override
1592 void didChangeLocales(List<Locale>? locales) {
1593 final Locale newLocale = _resolveLocales(locales, widget.supportedLocales);
1594 if (newLocale != _locale) {
1595 setState(() {
1596 _locale = newLocale;
1597 });
1598 }
1599 }
1600
1601 // Combine the Localizations for Widgets with the ones contributed
1602 // by the localizationsDelegates parameter, if any. Only the first delegate
1603 // of a particular LocalizationsDelegate.type is loaded so the
1604 // localizationsDelegate parameter can be used to override
1605 // WidgetsLocalizations.delegate.
1606 Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates {
1607 return <LocalizationsDelegate<dynamic>>[
1608 if (widget.localizationsDelegates != null)
1609 ...widget.localizationsDelegates!,
1610 DefaultWidgetsLocalizations.delegate,
1611 ];
1612 }
1613
1614 // BUILDER
1615
1616 bool _debugCheckLocalizations(Locale appLocale) {
1617 assert(() {
1618 final Set<Type> unsupportedTypes =
1619 _localizationsDelegates.map<Type>((LocalizationsDelegate<dynamic> delegate) => delegate.type).toSet();
1620 for (final LocalizationsDelegate<dynamic> delegate in _localizationsDelegates) {
1621 if (!unsupportedTypes.contains(delegate.type)) {
1622 continue;
1623 }
1624 if (delegate.isSupported(appLocale)) {
1625 unsupportedTypes.remove(delegate.type);
1626 }
1627 }
1628 if (unsupportedTypes.isEmpty) {
1629 return true;
1630 }
1631
1632 FlutterError.reportError(FlutterErrorDetails(
1633 exception: "Warning: This application's locale, $appLocale, is not supported by all of its localization delegates.",
1634 library: 'widgets',
1635 informationCollector: () => <DiagnosticsNode>[
1636 for (final Type unsupportedType in unsupportedTypes)
1637 ErrorDescription(
1638 '• A $unsupportedType delegate that supports the $appLocale locale was not found.',
1639 ),
1640 ErrorSpacer(),
1641 if (unsupportedTypes.length == 1 && unsupportedTypes.single.toString() == 'CupertinoLocalizations')
1642 // We previously explicitly avoided checking for this class so it's not uncommon for applications
1643 // to have omitted importing the required delegate.
1644 ...<DiagnosticsNode>[
1645 ErrorHint(
1646 'If the application is built using GlobalMaterialLocalizations.delegate, consider using '
1647 'GlobalMaterialLocalizations.delegates (plural) instead, as that will automatically declare '
1648 'the appropriate Cupertino localizations.'
1649 ),
1650 ErrorSpacer(),
1651 ],
1652 ErrorHint(
1653 'The declared supported locales for this app are: ${widget.supportedLocales.join(", ")}'
1654 ),
1655 ErrorSpacer(),
1656 ErrorDescription(
1657 'See https://flutter.dev/tutorials/internationalization/ for more '
1658 "information about configuring an app's locale, supportedLocales, "
1659 'and localizationsDelegates parameters.',
1660 ),
1661 ],
1662 ));
1663 return true;
1664 }());
1665 return true;
1666 }
1667
1668 @override
1669 Widget build(BuildContext context) {
1670 Widget? routing;
1671 if (_usesRouterWithDelegates) {
1672 routing = Router<Object>(
1673 restorationScopeId: 'router',
1674 routeInformationProvider: _effectiveRouteInformationProvider,
1675 routeInformationParser: widget.routeInformationParser,
1676 routerDelegate: widget.routerDelegate!,
1677 backButtonDispatcher: _effectiveBackButtonDispatcher,
1678 );
1679 } else if (_usesNavigator) {
1680 assert(_navigator != null);
1681 routing = FocusScope(
1682 debugLabel: 'Navigator Scope',
1683 autofocus: true,
1684 child: Navigator(
1685 clipBehavior: Clip.none,
1686 restorationScopeId: 'nav',
1687 key: _navigator,
1688 initialRoute: _initialRouteName,
1689 onGenerateRoute: _onGenerateRoute,
1690 onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null
1691 ? Navigator.defaultGenerateInitialRoutes
1692 : (NavigatorState navigator, String initialRouteName) {
1693 return widget.onGenerateInitialRoutes!(initialRouteName);
1694 },
1695 onUnknownRoute: _onUnknownRoute,
1696 observers: widget.navigatorObservers!,
1697 routeTraversalEdgeBehavior: kIsWeb ? TraversalEdgeBehavior.leaveFlutterView : TraversalEdgeBehavior.parentScope,
1698 reportsRouteUpdateToEngine: true,
1699 ),
1700 );
1701 } else if (_usesRouterWithConfig) {
1702 routing = Router<Object>.withConfig(
1703 restorationScopeId: 'router',
1704 config: widget.routerConfig!,
1705 );
1706 }
1707
1708 Widget result;
1709 if (widget.builder != null) {
1710 result = Builder(
1711 builder: (BuildContext context) {
1712 return widget.builder!(context, routing);
1713 },
1714 );
1715 } else {
1716 assert(routing != null);
1717 result = routing!;
1718 }
1719
1720 if (widget.textStyle != null) {
1721 result = DefaultTextStyle(
1722 style: widget.textStyle!,
1723 child: result,
1724 );
1725 }
1726
1727 PerformanceOverlay? performanceOverlay;
1728 // We need to push a performance overlay if any of the display or checkerboarding
1729 // options are set.
1730 if (widget.showPerformanceOverlay || WidgetsApp.showPerformanceOverlayOverride) {
1731 performanceOverlay = PerformanceOverlay.allEnabled(
1732 checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
1733 checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
1734 );
1735 } else if (widget.checkerboardRasterCacheImages || widget.checkerboardOffscreenLayers) {
1736 performanceOverlay = PerformanceOverlay(
1737 checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
1738 checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
1739 );
1740 }
1741 if (performanceOverlay != null) {
1742 result = Stack(
1743 children: <Widget>[
1744 result,
1745 Positioned(top: 0.0, left: 0.0, right: 0.0, child: performanceOverlay),
1746 ],
1747 );
1748 }
1749
1750 if (widget.showSemanticsDebugger) {
1751 result = SemanticsDebugger(
1752 child: result,
1753 );
1754 }
1755
1756 assert(() {
1757 result = ValueListenableBuilder<bool>(
1758 valueListenable: WidgetsApp._debugShowWidgetInspectorOverrideNotifier,
1759 builder: (BuildContext context, bool debugShowWidgetInspectorOverride, Widget? child) {
1760 if (widget.debugShowWidgetInspector || debugShowWidgetInspectorOverride) {
1761 return WidgetInspector(
1762 selectButtonBuilder: widget.inspectorSelectButtonBuilder,
1763 child: child!,
1764 );
1765 }
1766 return child!;
1767 },
1768 child: result,
1769 );
1770 if (widget.debugShowCheckedModeBanner && WidgetsApp.debugAllowBannerOverride) {
1771 result = CheckedModeBanner(
1772 child: result,
1773 );
1774 }
1775 return true;
1776 }());
1777
1778 final Widget title;
1779 if (widget.onGenerateTitle != null) {
1780 title = Builder(
1781 // This Builder exists to provide a context below the Localizations widget.
1782 // The onGenerateTitle callback can refer to Localizations via its context
1783 // parameter.
1784 builder: (BuildContext context) {
1785 final String title = widget.onGenerateTitle!(context);
1786 return Title(
1787 title: title,
1788 color: widget.color.withOpacity(1.0),
1789 child: result,
1790 );
1791 },
1792 );
1793 } else {
1794 title = Title(
1795 title: widget.title,
1796 color: widget.color.withOpacity(1.0),
1797 child: result,
1798 );
1799 }
1800
1801 final Locale appLocale = widget.locale != null
1802 ? _resolveLocales(<Locale>[widget.locale!], widget.supportedLocales)
1803 : _locale!;
1804
1805 assert(_debugCheckLocalizations(appLocale));
1806
1807 return RootRestorationScope(
1808 restorationId: widget.restorationScopeId,
1809 child: SharedAppData(
1810 child: NotificationListener<NavigationNotification>(
1811 onNotification: widget.onNavigationNotification ?? _defaultOnNavigationNotification,
1812 child: Shortcuts(
1813 debugLabel: '<Default WidgetsApp Shortcuts>',
1814 shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
1815 // DefaultTextEditingShortcuts is nested inside Shortcuts so that it can
1816 // fall through to the defaultShortcuts.
1817 child: DefaultTextEditingShortcuts(
1818 child: Actions(
1819 actions: widget.actions ?? <Type, Action<Intent>>{
1820 ...WidgetsApp.defaultActions,
1821 ScrollIntent: Action<ScrollIntent>.overridable(context: context, defaultAction: ScrollAction()),
1822 },
1823 child: FocusTraversalGroup(
1824 policy: ReadingOrderTraversalPolicy(),
1825 child: TapRegionSurface(
1826 child: ShortcutRegistrar(
1827 child: Localizations(
1828 locale: appLocale,
1829 delegates: _localizationsDelegates.toList(),
1830 child: title,
1831 ),
1832 ),
1833 ),
1834 ),
1835 ),
1836 ),
1837 ),
1838 ),
1839 ),
1840 );
1841 }
1842}
1843