Implement Router widget and widgets app api (#60299)
This commit is contained in:
parent
12b8d9db80
commit
f9fd71bc78
@ -104,6 +104,50 @@ class CupertinoApp extends StatefulWidget {
|
||||
assert(checkerboardOffscreenLayers != null),
|
||||
assert(showSemanticsDebugger != null),
|
||||
assert(debugShowCheckedModeBanner != null),
|
||||
routeInformationProvider = null,
|
||||
routeInformationParser = null,
|
||||
routerDelegate = null,
|
||||
backButtonDispatcher = null,
|
||||
super(key: key);
|
||||
|
||||
/// Creates a [CupertinoApp] that uses the [Router] instead of a [Navigator].
|
||||
const CupertinoApp.router({
|
||||
Key key,
|
||||
this.routeInformationProvider,
|
||||
@required this.routeInformationParser,
|
||||
@required this.routerDelegate,
|
||||
this.backButtonDispatcher,
|
||||
this.theme,
|
||||
this.builder,
|
||||
this.title = '',
|
||||
this.onGenerateTitle,
|
||||
this.color,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeListResolutionCallback,
|
||||
this.localeResolutionCallback,
|
||||
this.supportedLocales = const <Locale>[Locale('en', 'US')],
|
||||
this.showPerformanceOverlay = false,
|
||||
this.checkerboardRasterCacheImages = false,
|
||||
this.checkerboardOffscreenLayers = false,
|
||||
this.showSemanticsDebugger = false,
|
||||
this.debugShowCheckedModeBanner = true,
|
||||
this.shortcuts,
|
||||
this.actions,
|
||||
}) : assert(title != null),
|
||||
assert(showPerformanceOverlay != null),
|
||||
assert(checkerboardRasterCacheImages != null),
|
||||
assert(checkerboardOffscreenLayers != null),
|
||||
assert(showSemanticsDebugger != null),
|
||||
assert(debugShowCheckedModeBanner != null),
|
||||
navigatorObservers = null,
|
||||
navigatorKey = null,
|
||||
onGenerateRoute = null,
|
||||
home = null,
|
||||
onGenerateInitialRoutes = null,
|
||||
onUnknownRoute = null,
|
||||
routes = null,
|
||||
initialRoute = null,
|
||||
super(key: key);
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.navigatorKey}
|
||||
@ -143,6 +187,18 @@ class CupertinoApp extends StatefulWidget {
|
||||
/// {@macro flutter.widgets.widgetsApp.navigatorObservers}
|
||||
final List<NavigatorObserver> navigatorObservers;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.routeInformationProvider}
|
||||
final RouteInformationProvider routeInformationProvider;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.routeInformationParser}
|
||||
final RouteInformationParser<Object> routeInformationParser;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.routerDelegate}
|
||||
final RouterDelegate<Object> routerDelegate;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
|
||||
final BackButtonDispatcher backButtonDispatcher;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.builder}
|
||||
final TransitionBuilder builder;
|
||||
|
||||
@ -286,6 +342,7 @@ class _AlwaysCupertinoScrollBehavior extends ScrollBehavior {
|
||||
|
||||
class _CupertinoAppState extends State<CupertinoApp> {
|
||||
HeroController _heroController;
|
||||
bool get _usesRouter => widget.routerDelegate != null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -304,6 +361,83 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
||||
yield DefaultCupertinoLocalizations.delegate;
|
||||
}
|
||||
|
||||
Widget _inspectorSelectButtonBuilder(BuildContext context, VoidCallback onPressed) {
|
||||
return CupertinoButton.filled(
|
||||
child: const Icon(
|
||||
CupertinoIcons.search,
|
||||
size: 28.0,
|
||||
color: CupertinoColors.white,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: onPressed,
|
||||
);
|
||||
}
|
||||
|
||||
WidgetsApp _buildWidgetApp(BuildContext context) {
|
||||
final CupertinoThemeData effectiveThemeData = CupertinoTheme.of(context);
|
||||
final Color color = CupertinoDynamicColor.resolve(widget.color ?? effectiveThemeData.primaryColor, context);
|
||||
|
||||
if (_usesRouter) {
|
||||
return WidgetsApp.router(
|
||||
key: GlobalObjectKey(this),
|
||||
routeInformationProvider: widget.routeInformationProvider,
|
||||
routeInformationParser: widget.routeInformationParser,
|
||||
routerDelegate: widget.routerDelegate,
|
||||
backButtonDispatcher: widget.backButtonDispatcher,
|
||||
builder: widget.builder,
|
||||
title: widget.title,
|
||||
onGenerateTitle: widget.onGenerateTitle,
|
||||
textStyle: effectiveThemeData.textTheme.textStyle,
|
||||
color: color,
|
||||
locale: widget.locale,
|
||||
localizationsDelegates: _localizationsDelegates,
|
||||
localeResolutionCallback: widget.localeResolutionCallback,
|
||||
localeListResolutionCallback: widget.localeListResolutionCallback,
|
||||
supportedLocales: widget.supportedLocales,
|
||||
showPerformanceOverlay: widget.showPerformanceOverlay,
|
||||
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
|
||||
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
|
||||
showSemanticsDebugger: widget.showSemanticsDebugger,
|
||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
);
|
||||
}
|
||||
return WidgetsApp(
|
||||
key: GlobalObjectKey(this),
|
||||
navigatorKey: widget.navigatorKey,
|
||||
navigatorObservers: widget.navigatorObservers,
|
||||
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
|
||||
return CupertinoPageRoute<T>(settings: settings, builder: builder);
|
||||
},
|
||||
home: widget.home,
|
||||
routes: widget.routes,
|
||||
initialRoute: widget.initialRoute,
|
||||
onGenerateRoute: widget.onGenerateRoute,
|
||||
onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
|
||||
onUnknownRoute: widget.onUnknownRoute,
|
||||
builder: widget.builder,
|
||||
title: widget.title,
|
||||
onGenerateTitle: widget.onGenerateTitle,
|
||||
textStyle: effectiveThemeData.textTheme.textStyle,
|
||||
color: color,
|
||||
locale: widget.locale,
|
||||
localizationsDelegates: _localizationsDelegates,
|
||||
localeResolutionCallback: widget.localeResolutionCallback,
|
||||
localeListResolutionCallback: widget.localeListResolutionCallback,
|
||||
supportedLocales: widget.supportedLocales,
|
||||
showPerformanceOverlay: widget.showPerformanceOverlay,
|
||||
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
|
||||
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
|
||||
showSemanticsDebugger: widget.showSemanticsDebugger,
|
||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final CupertinoThemeData effectiveThemeData = widget.theme ?? const CupertinoThemeData();
|
||||
@ -314,53 +448,11 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
||||
data: CupertinoUserInterfaceLevelData.base,
|
||||
child: CupertinoTheme(
|
||||
data: effectiveThemeData,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return HeroControllerScope(
|
||||
controller: _heroController,
|
||||
child: WidgetsApp(
|
||||
key: GlobalObjectKey(this),
|
||||
navigatorKey: widget.navigatorKey,
|
||||
navigatorObservers: widget.navigatorObservers,
|
||||
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) =>
|
||||
CupertinoPageRoute<T>(settings: settings, builder: builder),
|
||||
home: widget.home,
|
||||
routes: widget.routes,
|
||||
initialRoute: widget.initialRoute,
|
||||
onGenerateRoute: widget.onGenerateRoute,
|
||||
onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
|
||||
onUnknownRoute: widget.onUnknownRoute,
|
||||
builder: widget.builder,
|
||||
title: widget.title,
|
||||
onGenerateTitle: widget.onGenerateTitle,
|
||||
textStyle: CupertinoTheme.of(context).textTheme.textStyle,
|
||||
color: CupertinoDynamicColor.resolve(widget.color ?? effectiveThemeData.primaryColor, context),
|
||||
locale: widget.locale,
|
||||
localizationsDelegates: _localizationsDelegates,
|
||||
localeResolutionCallback: widget.localeResolutionCallback,
|
||||
localeListResolutionCallback: widget.localeListResolutionCallback,
|
||||
supportedLocales: widget.supportedLocales,
|
||||
showPerformanceOverlay: widget.showPerformanceOverlay,
|
||||
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
|
||||
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
|
||||
showSemanticsDebugger: widget.showSemanticsDebugger,
|
||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||
inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
|
||||
return CupertinoButton.filled(
|
||||
child: const Icon(
|
||||
CupertinoIcons.search,
|
||||
size: 28.0,
|
||||
color: CupertinoColors.white,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: onPressed,
|
||||
);
|
||||
},
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: HeroControllerScope(
|
||||
controller: _heroController,
|
||||
child: Builder(
|
||||
builder: _buildWidgetApp,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -7,6 +7,7 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@ -205,6 +206,58 @@ class MaterialApp extends StatefulWidget {
|
||||
assert(checkerboardOffscreenLayers != null),
|
||||
assert(showSemanticsDebugger != null),
|
||||
assert(debugShowCheckedModeBanner != null),
|
||||
routeInformationProvider = null,
|
||||
routeInformationParser = null,
|
||||
routerDelegate = null,
|
||||
backButtonDispatcher = null,
|
||||
super(key: key);
|
||||
|
||||
/// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
|
||||
const MaterialApp.router({
|
||||
Key key,
|
||||
this.routeInformationProvider,
|
||||
@required this.routeInformationParser,
|
||||
@required this.routerDelegate,
|
||||
this.backButtonDispatcher,
|
||||
this.builder,
|
||||
this.title = '',
|
||||
this.onGenerateTitle,
|
||||
this.color,
|
||||
this.theme,
|
||||
this.darkTheme,
|
||||
this.highContrastTheme,
|
||||
this.highContrastDarkTheme,
|
||||
this.themeMode = ThemeMode.system,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeListResolutionCallback,
|
||||
this.localeResolutionCallback,
|
||||
this.supportedLocales = const <Locale>[Locale('en', 'US')],
|
||||
this.debugShowMaterialGrid = false,
|
||||
this.showPerformanceOverlay = false,
|
||||
this.checkerboardRasterCacheImages = false,
|
||||
this.checkerboardOffscreenLayers = false,
|
||||
this.showSemanticsDebugger = false,
|
||||
this.debugShowCheckedModeBanner = true,
|
||||
this.shortcuts,
|
||||
this.actions,
|
||||
}) : assert(routeInformationParser != null),
|
||||
assert(routerDelegate != null),
|
||||
assert(title != null),
|
||||
assert(debugShowMaterialGrid != null),
|
||||
assert(showPerformanceOverlay != null),
|
||||
assert(checkerboardRasterCacheImages != null),
|
||||
assert(checkerboardOffscreenLayers != null),
|
||||
assert(showSemanticsDebugger != null),
|
||||
assert(debugShowCheckedModeBanner != null),
|
||||
navigatorObservers = null,
|
||||
navigatorKey = null,
|
||||
onGenerateRoute = null,
|
||||
home = null,
|
||||
onGenerateInitialRoutes = null,
|
||||
onUnknownRoute = null,
|
||||
routes = null,
|
||||
initialRoute = null,
|
||||
super(key: key);
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.navigatorKey}
|
||||
@ -238,6 +291,18 @@ class MaterialApp extends StatefulWidget {
|
||||
/// {@macro flutter.widgets.widgetsApp.navigatorObservers}
|
||||
final List<NavigatorObserver> navigatorObservers;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.routeInformationProvider}
|
||||
final RouteInformationProvider routeInformationProvider;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.routeInformationParser}
|
||||
final RouteInformationParser<Object> routeInformationParser;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.routerDelegate}
|
||||
final RouterDelegate<Object> routerDelegate;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
|
||||
final BackButtonDispatcher backButtonDispatcher;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.builder}
|
||||
///
|
||||
/// Material specific features such as [showDialog] and [showMenu], and widgets
|
||||
@ -611,6 +676,8 @@ class _MaterialScrollBehavior extends ScrollBehavior {
|
||||
class _MaterialAppState extends State<MaterialApp> {
|
||||
HeroController _heroController;
|
||||
|
||||
bool get _usesRouter => widget.routerDelegate != null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -629,75 +696,77 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
yield DefaultCupertinoLocalizations.delegate;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget result = HeroControllerScope(
|
||||
controller: _heroController,
|
||||
child: WidgetsApp(
|
||||
Widget _inspectorSelectButtonBuilder(BuildContext context, VoidCallback onPressed) {
|
||||
return FloatingActionButton(
|
||||
child: const Icon(Icons.search),
|
||||
onPressed: onPressed,
|
||||
mini: true,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _materialBuilder(BuildContext context, Widget child) {
|
||||
// Resolve which theme to use based on brightness and high contrast.
|
||||
final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
|
||||
final Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
|
||||
final bool useDarkTheme = mode == ThemeMode.dark
|
||||
|| (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark);
|
||||
final bool highContrast = MediaQuery.highContrastOf(context);
|
||||
ThemeData theme;
|
||||
|
||||
if (useDarkTheme && highContrast && widget.highContrastDarkTheme != null) {
|
||||
theme = widget.highContrastDarkTheme;
|
||||
} else if (useDarkTheme && widget.darkTheme != null) {
|
||||
theme = widget.darkTheme;
|
||||
} else if (highContrast && widget.highContrastTheme != null) {
|
||||
theme = widget.highContrastTheme;
|
||||
}
|
||||
theme ??= widget.theme ?? ThemeData.light();
|
||||
|
||||
return AnimatedTheme(
|
||||
data: theme,
|
||||
isMaterialAppTheme: true,
|
||||
child: widget.builder != null
|
||||
? Builder(
|
||||
builder: (BuildContext context) {
|
||||
// Why are we surrounding a builder with a builder?
|
||||
//
|
||||
// The widget.builder may contain code that invokes
|
||||
// Theme.of(), which should return the theme we selected
|
||||
// above in AnimatedTheme. However, if we invoke
|
||||
// widget.builder() directly as the child of AnimatedTheme
|
||||
// then there is no Context separating them, and the
|
||||
// widget.builder() will not find the theme. Therefore, we
|
||||
// surround widget.builder with yet another builder so that
|
||||
// a context separates them and Theme.of() correctly
|
||||
// resolves to the theme we passed to AnimatedTheme.
|
||||
return widget.builder(context, child);
|
||||
},
|
||||
)
|
||||
: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWidgetApp(BuildContext context) {
|
||||
// The color property is always pulled from the light theme, even if dark
|
||||
// mode is activated. This was done to simplify the technical details
|
||||
// of switching themes and it was deemed acceptable because this color
|
||||
// property is only used on old Android OSes to color the app bar in
|
||||
// Android's switcher UI.
|
||||
//
|
||||
// blue is the primary color of the default theme.
|
||||
final Color materialColor = widget.color ?? widget.theme?.primaryColor ?? Colors.blue;
|
||||
if (_usesRouter) {
|
||||
return WidgetsApp.router(
|
||||
key: GlobalObjectKey(this),
|
||||
navigatorKey: widget.navigatorKey,
|
||||
navigatorObservers: widget.navigatorObservers,
|
||||
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
|
||||
return MaterialPageRoute<T>(settings: settings, builder: builder);
|
||||
},
|
||||
home: widget.home,
|
||||
routes: widget.routes,
|
||||
initialRoute: widget.initialRoute,
|
||||
onGenerateRoute: widget.onGenerateRoute,
|
||||
onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
|
||||
onUnknownRoute: widget.onUnknownRoute,
|
||||
builder: (BuildContext context, Widget child) {
|
||||
// Resolve which theme to use based on brightness and high contrast.
|
||||
final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
|
||||
final Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
|
||||
final bool useDarkTheme = mode == ThemeMode.dark
|
||||
|| (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark);
|
||||
final bool highContrast = MediaQuery.highContrastOf(context);
|
||||
ThemeData theme;
|
||||
|
||||
if (useDarkTheme && highContrast && widget.highContrastDarkTheme != null) {
|
||||
theme = widget.highContrastDarkTheme;
|
||||
} else if (useDarkTheme && widget.darkTheme != null) {
|
||||
theme = widget.darkTheme;
|
||||
} else if (highContrast && widget.highContrastTheme != null) {
|
||||
theme = widget.highContrastTheme;
|
||||
}
|
||||
theme ??= widget.theme ?? ThemeData.light();
|
||||
|
||||
return AnimatedTheme(
|
||||
data: theme,
|
||||
isMaterialAppTheme: true,
|
||||
child: widget.builder != null
|
||||
? Builder(
|
||||
builder: (BuildContext context) {
|
||||
// Why are we surrounding a builder with a builder?
|
||||
//
|
||||
// The widget.builder may contain code that invokes
|
||||
// Theme.of(), which should return the theme we selected
|
||||
// above in AnimatedTheme. However, if we invoke
|
||||
// widget.builder() directly as the child of AnimatedTheme
|
||||
// then there is no Context separating them, and the
|
||||
// widget.builder() will not find the theme. Therefore, we
|
||||
// surround widget.builder with yet another builder so that
|
||||
// a context separates them and Theme.of() correctly
|
||||
// resolves to the theme we passed to AnimatedTheme.
|
||||
return widget.builder(context, child);
|
||||
},
|
||||
)
|
||||
: child,
|
||||
);
|
||||
},
|
||||
routeInformationProvider: widget.routeInformationProvider,
|
||||
routeInformationParser: widget.routeInformationParser,
|
||||
routerDelegate: widget.routerDelegate,
|
||||
backButtonDispatcher: widget.backButtonDispatcher,
|
||||
builder: _materialBuilder,
|
||||
title: widget.title,
|
||||
onGenerateTitle: widget.onGenerateTitle,
|
||||
textStyle: _errorTextStyle,
|
||||
// The color property is always pulled from the light theme, even if dark
|
||||
// mode is activated. This was done to simplify the technical details
|
||||
// of switching themes and it was deemed acceptable because this color
|
||||
// property is only used on old Android OSes to color the app bar in
|
||||
// Android's switcher UI.
|
||||
//
|
||||
// blue is the primary color of the default theme
|
||||
color: widget.color ?? widget.theme?.primaryColor ?? Colors.blue,
|
||||
color: materialColor,
|
||||
locale: widget.locale,
|
||||
localizationsDelegates: _localizationsDelegates,
|
||||
localeResolutionCallback: widget.localeResolutionCallback,
|
||||
@ -708,17 +777,49 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
|
||||
showSemanticsDebugger: widget.showSemanticsDebugger,
|
||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||
inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
|
||||
return FloatingActionButton(
|
||||
child: const Icon(Icons.search),
|
||||
onPressed: onPressed,
|
||||
mini: true,
|
||||
);
|
||||
},
|
||||
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return WidgetsApp(
|
||||
key: GlobalObjectKey(this),
|
||||
navigatorKey: widget.navigatorKey,
|
||||
navigatorObservers: widget.navigatorObservers,
|
||||
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
|
||||
return MaterialPageRoute<T>(settings: settings, builder: builder);
|
||||
},
|
||||
home: widget.home,
|
||||
routes: widget.routes,
|
||||
initialRoute: widget.initialRoute,
|
||||
onGenerateRoute: widget.onGenerateRoute,
|
||||
onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
|
||||
onUnknownRoute: widget.onUnknownRoute,
|
||||
builder: _materialBuilder,
|
||||
title: widget.title,
|
||||
onGenerateTitle: widget.onGenerateTitle,
|
||||
textStyle: _errorTextStyle,
|
||||
color: materialColor,
|
||||
locale: widget.locale,
|
||||
localizationsDelegates: _localizationsDelegates,
|
||||
localeResolutionCallback: widget.localeResolutionCallback,
|
||||
localeListResolutionCallback: widget.localeListResolutionCallback,
|
||||
supportedLocales: widget.supportedLocales,
|
||||
showPerformanceOverlay: widget.showPerformanceOverlay,
|
||||
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
|
||||
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
|
||||
showSemanticsDebugger: widget.showSemanticsDebugger,
|
||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget result = _buildWidgetApp(context);
|
||||
|
||||
assert(() {
|
||||
if (widget.debugShowMaterialGrid) {
|
||||
@ -735,7 +836,10 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
|
||||
return ScrollConfiguration(
|
||||
behavior: _MaterialScrollBehavior(),
|
||||
child: result,
|
||||
child: HeroControllerScope(
|
||||
controller: _heroController,
|
||||
child: result,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -26,16 +26,18 @@ class SystemChannels {
|
||||
/// * `pushRoute`, which is called with a single string argument when the
|
||||
/// operating system instructs the application to open a particular page.
|
||||
///
|
||||
/// * `pushRouteInformation`, which is called with a map, which contains a
|
||||
/// location string and a state object, when the operating system instructs
|
||||
/// the application to open a particular page. These parameters are stored
|
||||
/// under the key `location` and `state` in the map.
|
||||
///
|
||||
/// The following methods are used for the opposite direction data flow. The
|
||||
/// framework notifies the engine about the route changes.
|
||||
///
|
||||
/// * `routePushed`, which is called when a route is pushed. (e.g. A modal
|
||||
/// replaces the entire screen.)
|
||||
/// * `routeUpdated`, which is called when current route has changed.
|
||||
///
|
||||
/// * `routePopped`, which is called when a route is popped. (e.g. A dialog,
|
||||
/// such as time picker is closed.)
|
||||
///
|
||||
/// * `routeReplaced`, which is called when a route is replaced.
|
||||
/// * `routeInformationUpdated`, which is called by the [Router] when the
|
||||
/// application navigate to a new location.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -46,7 +48,7 @@ class SystemChannels {
|
||||
/// [Navigator.push], [Navigator.pushReplacement], [Navigator.pop] and
|
||||
/// [Navigator.replace], utilize this channel's methods to send route
|
||||
/// change information from framework to engine.
|
||||
static const MethodChannel navigation = MethodChannel(
|
||||
static const MethodChannel navigation = OptionalMethodChannel(
|
||||
'flutter/navigation',
|
||||
JSONMethodCodec(),
|
||||
);
|
||||
|
@ -35,4 +35,37 @@ class SystemNavigator {
|
||||
static Future<void> pop({bool? animated}) async {
|
||||
await SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop', animated);
|
||||
}
|
||||
|
||||
/// Notifies the platform for a route information change.
|
||||
///
|
||||
/// On Web, creates a new browser history entry and update URL with the route
|
||||
/// information.
|
||||
static void routeInformationUpdated({
|
||||
required String location,
|
||||
Object? state
|
||||
}) {
|
||||
SystemChannels.navigation.invokeMethod<void>(
|
||||
'routeInformationUpdated',
|
||||
<String, dynamic>{
|
||||
'location': location,
|
||||
'state': state,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Notifies the platform of a route change.
|
||||
///
|
||||
/// On Web, updates the URL bar with the [routeName].
|
||||
static void routeUpdated({
|
||||
String? routeName,
|
||||
String? previousRouteName
|
||||
}) {
|
||||
SystemChannels.navigation.invokeMethod<void>(
|
||||
'routeUpdated',
|
||||
<String, dynamic>{
|
||||
'previousRouteName': previousRouteName,
|
||||
'routeName': routeName,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import 'media_query.dart';
|
||||
import 'navigator.dart';
|
||||
import 'pages.dart';
|
||||
import 'performance_overlay.dart';
|
||||
import 'router.dart';
|
||||
import 'scrollable.dart';
|
||||
import 'semantics_debugger.dart';
|
||||
import 'shortcuts.dart';
|
||||
@ -260,6 +261,62 @@ class WidgetsApp extends StatefulWidget {
|
||||
assert(showSemanticsDebugger != null),
|
||||
assert(debugShowCheckedModeBanner != null),
|
||||
assert(debugShowWidgetInspector != null),
|
||||
routeInformationProvider = null,
|
||||
routeInformationParser = null,
|
||||
routerDelegate = null,
|
||||
backButtonDispatcher = null,
|
||||
super(key: key);
|
||||
|
||||
/// Creates a [WidgetsApp] that uses the [Router] instead of a [Navigator].
|
||||
WidgetsApp.router({
|
||||
Key key,
|
||||
this.routeInformationProvider,
|
||||
@required this.routeInformationParser,
|
||||
@required this.routerDelegate,
|
||||
BackButtonDispatcher backButtonDispatcher,
|
||||
this.builder,
|
||||
this.title = '',
|
||||
this.onGenerateTitle,
|
||||
this.textStyle,
|
||||
@required this.color,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeListResolutionCallback,
|
||||
this.localeResolutionCallback,
|
||||
this.supportedLocales = const <Locale>[Locale('en', 'US')],
|
||||
this.showPerformanceOverlay = false,
|
||||
this.checkerboardRasterCacheImages = false,
|
||||
this.checkerboardOffscreenLayers = false,
|
||||
this.showSemanticsDebugger = false,
|
||||
this.debugShowWidgetInspector = false,
|
||||
this.debugShowCheckedModeBanner = true,
|
||||
this.inspectorSelectButtonBuilder,
|
||||
this.shortcuts,
|
||||
this.actions,
|
||||
}) : assert(
|
||||
routeInformationParser != null &&
|
||||
routerDelegate != null,
|
||||
'The routeInformationParser and routerDelegate cannot be null.'
|
||||
),
|
||||
assert(title != null),
|
||||
assert(color != null),
|
||||
assert(supportedLocales != null && supportedLocales.isNotEmpty),
|
||||
assert(showPerformanceOverlay != null),
|
||||
assert(checkerboardRasterCacheImages != null),
|
||||
assert(checkerboardOffscreenLayers != null),
|
||||
assert(showSemanticsDebugger != null),
|
||||
assert(debugShowCheckedModeBanner != null),
|
||||
assert(debugShowWidgetInspector != null),
|
||||
navigatorObservers = null,
|
||||
backButtonDispatcher = backButtonDispatcher ?? RootBackButtonDispatcher(),
|
||||
navigatorKey = null,
|
||||
onGenerateRoute = null,
|
||||
pageRouteBuilder = null,
|
||||
home = null,
|
||||
onGenerateInitialRoutes = null,
|
||||
onUnknownRoute = null,
|
||||
routes = null,
|
||||
initialRoute = null,
|
||||
super(key: key);
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.navigatorKey}
|
||||
@ -321,6 +378,71 @@ class WidgetsApp extends StatefulWidget {
|
||||
/// or a [CupertinoPageRoute] should be used for building page transitions.
|
||||
final PageRouteFactory pageRouteBuilder;
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.routeInformationParser}
|
||||
/// A delegate to parse the route information from the
|
||||
/// [routeInformationProvider] into a generic data type to be processed by
|
||||
/// the [routerDelegate] at a later stage.
|
||||
///
|
||||
/// This object will be used by the underlying [Router].
|
||||
///
|
||||
/// The generic type `T` must match the generic type of the [routerDelegate].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Router.routeInformationParser]: which receives this object when this
|
||||
/// widget builds the [Router].
|
||||
/// {@endtemplate}
|
||||
final RouteInformationParser<Object> routeInformationParser;
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.routerDelegate}
|
||||
/// A delegate that configures a widget, typically a [Navigator], with
|
||||
/// parsed result from the [routeInformationParser].
|
||||
///
|
||||
/// This object will be used by the underlying [Router].
|
||||
///
|
||||
/// The generic type `T` must match the generic type of the
|
||||
/// [routeInformationParser].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Router.routerDelegate]: which receives this object when this widget
|
||||
/// builds the [Router].
|
||||
/// {@endtemplate}
|
||||
final RouterDelegate<Object> routerDelegate;
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.backButtonDispatcher}
|
||||
/// A delegate that decide whether to handle the Android back button intent.
|
||||
///
|
||||
/// This object will be used by the underlying [Router].
|
||||
///
|
||||
/// If this is not provided, the widgets app will create a
|
||||
/// [RootBackButtonDispatcher] by default.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Router.backButtonDispatcher]: which receives this object when this
|
||||
/// widget builds the [Router].
|
||||
/// {@endtemplate}
|
||||
final BackButtonDispatcher backButtonDispatcher;
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.routeInformationProvider}
|
||||
/// A object that provides route information through the
|
||||
/// [RouteInformationProvider.value] and notifies its listener when its value
|
||||
/// changes.
|
||||
///
|
||||
/// This object will be used by the underlying [Router].
|
||||
///
|
||||
/// If this is not provided, the widgets app will create a
|
||||
/// [PlatformRouteInformationProvider] with initial route name equals to
|
||||
/// the [Window.defaultRouteName] by default.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Router.routeInformationProvider]: which receives this object when this
|
||||
/// widget builds the [Router].
|
||||
/// {@endtemplate}
|
||||
final RouteInformationProvider routeInformationProvider;
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.home}
|
||||
/// The widget for the default route of the app ([Navigator.defaultRouteName],
|
||||
/// which is `/`).
|
||||
@ -960,10 +1082,21 @@ class WidgetsApp extends StatefulWidget {
|
||||
class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
// STATE LIFECYCLE
|
||||
|
||||
// If window.defaultRouteName isn't '/', we should assume it was set
|
||||
// intentionally via `setInitialRoute`, and should override whatever is in
|
||||
// [widget.initialRoute].
|
||||
String get _initialRouteName => WidgetsBinding.instance.window.defaultRouteName != Navigator.defaultRouteName
|
||||
? WidgetsBinding.instance.window.defaultRouteName
|
||||
: widget.initialRoute ?? WidgetsBinding.instance.window.defaultRouteName;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateNavigator();
|
||||
if (_usesRouter) {
|
||||
_updateRouter();
|
||||
} else {
|
||||
_updateNavigator();
|
||||
}
|
||||
_locale = _resolveLocales(WidgetsBinding.instance.window.locales, widget.supportedLocales);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
@ -971,16 +1104,37 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
@override
|
||||
void didUpdateWidget(WidgetsApp oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.navigatorKey != oldWidget.navigatorKey)
|
||||
if (oldWidget.routeInformationProvider != widget.routeInformationProvider) {
|
||||
_updateRouter();
|
||||
}
|
||||
if (widget.navigatorKey != oldWidget.navigatorKey) {
|
||||
_updateNavigator();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_defaultRouteInformationProvider?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get _usesRouter => widget.routerDelegate != null;
|
||||
|
||||
// ROUTER
|
||||
RouteInformationProvider get _effectiveRouteInformationProvider => widget.routeInformationProvider ?? _defaultRouteInformationProvider;
|
||||
PlatformRouteInformationProvider _defaultRouteInformationProvider;
|
||||
|
||||
void _updateRouter() {
|
||||
_defaultRouteInformationProvider?.dispose();
|
||||
if (widget.routeInformationProvider == null)
|
||||
_defaultRouteInformationProvider = PlatformRouteInformationProvider(
|
||||
initialRouteInformation: RouteInformation(
|
||||
location: _initialRouteName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// NAVIGATOR
|
||||
|
||||
GlobalKey<NavigatorState> _navigator;
|
||||
@ -1050,6 +1204,11 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
@override
|
||||
Future<bool> didPopRoute() async {
|
||||
assert(mounted);
|
||||
// The back button dispatcher should handle the pop route if we use a
|
||||
// router.
|
||||
if (_usesRouter)
|
||||
return false;
|
||||
|
||||
final NavigatorState navigator = _navigator?.currentState;
|
||||
if (navigator == null)
|
||||
return false;
|
||||
@ -1059,6 +1218,11 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
@override
|
||||
Future<bool> didPushRoute(String route) async {
|
||||
assert(mounted);
|
||||
// The route name provider should handle the push route if we uses a
|
||||
// router.
|
||||
if (_usesRouter)
|
||||
return false;
|
||||
|
||||
final NavigatorState navigator = _navigator?.currentState;
|
||||
if (navigator == null)
|
||||
return false;
|
||||
@ -1291,16 +1455,20 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget navigator;
|
||||
if (_navigator != null) {
|
||||
navigator = Navigator(
|
||||
Widget routing;
|
||||
if (_usesRouter) {
|
||||
assert(_effectiveRouteInformationProvider != null);
|
||||
routing = Router<dynamic>(
|
||||
routeInformationProvider: _effectiveRouteInformationProvider,
|
||||
routeInformationParser: widget.routeInformationParser,
|
||||
routerDelegate: widget.routerDelegate,
|
||||
backButtonDispatcher: widget.backButtonDispatcher,
|
||||
);
|
||||
} else {
|
||||
assert(_navigator != null);
|
||||
routing = Navigator(
|
||||
key: _navigator,
|
||||
// If window.defaultRouteName isn't '/', we should assume it was set
|
||||
// intentionally via `setInitialRoute`, and should override whatever
|
||||
// is in [widget.initialRoute].
|
||||
initialRoute: WidgetsBinding.instance.window.defaultRouteName != Navigator.defaultRouteName
|
||||
? WidgetsBinding.instance.window.defaultRouteName
|
||||
: widget.initialRoute ?? WidgetsBinding.instance.window.defaultRouteName,
|
||||
initialRoute: _initialRouteName,
|
||||
onGenerateRoute: _onGenerateRoute,
|
||||
onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null
|
||||
? Navigator.defaultGenerateInitialRoutes
|
||||
@ -1317,12 +1485,12 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
if (widget.builder != null) {
|
||||
result = Builder(
|
||||
builder: (BuildContext context) {
|
||||
return widget.builder(context, navigator);
|
||||
return widget.builder(context, routing);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
assert(navigator != null);
|
||||
result = navigator;
|
||||
assert(routing != null);
|
||||
result = routing;
|
||||
}
|
||||
|
||||
if (widget.textStyle != null) {
|
||||
|
@ -18,6 +18,7 @@ import 'app.dart';
|
||||
import 'debug.dart';
|
||||
import 'focus_manager.dart';
|
||||
import 'framework.dart';
|
||||
import 'router.dart';
|
||||
import 'widget_inspector.dart';
|
||||
|
||||
export 'dart:ui' show AppLifecycleState, Locale;
|
||||
@ -95,7 +96,7 @@ abstract class WidgetsBindingObserver {
|
||||
/// [SystemChannels.navigation].
|
||||
Future<bool> didPopRoute() => Future<bool>.value(false);
|
||||
|
||||
/// Called when the host tells the app to push a new route onto the
|
||||
/// Called when the host tells the application to push a new route onto the
|
||||
/// navigator.
|
||||
///
|
||||
/// Observers are expected to return true if they were able to
|
||||
@ -106,6 +107,22 @@ abstract class WidgetsBindingObserver {
|
||||
/// [SystemChannels.navigation].
|
||||
Future<bool> didPushRoute(String route) => Future<bool>.value(false);
|
||||
|
||||
/// Called when the host tells the application to push a new
|
||||
/// [RouteInformation] and a restoration state onto the router.
|
||||
///
|
||||
/// Observers are expected to return true if they were able to
|
||||
/// handle the notification. Observers are notified in registration
|
||||
/// order until one returns true.
|
||||
///
|
||||
/// This method exposes the `pushRouteInformation` notification from
|
||||
/// [SystemChannels.navigation].
|
||||
///
|
||||
/// The default implementation is to call the [didPushRoute] directly with the
|
||||
/// [RouteInformation.location].
|
||||
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
|
||||
return didPushRoute(routeInformation.location);
|
||||
}
|
||||
|
||||
/// Called when the application's dimensions change. For example,
|
||||
/// when a phone is rotated.
|
||||
///
|
||||
@ -654,12 +671,28 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handlePushRouteInformation(Map<dynamic, dynamic> routeArguments) async {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
|
||||
if (
|
||||
await observer.didPushRouteInformation(
|
||||
RouteInformation(
|
||||
location: routeArguments['location'] as String,
|
||||
state: routeArguments['state'] as Object,
|
||||
)
|
||||
)
|
||||
)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
|
||||
switch (methodCall.method) {
|
||||
case 'popRoute':
|
||||
return handlePopRoute();
|
||||
case 'pushRoute':
|
||||
return handlePushRoute(methodCall.arguments as String);
|
||||
case 'pushRouteInformation':
|
||||
return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>);
|
||||
}
|
||||
return Future<dynamic>.value();
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import 'focus_scope.dart';
|
||||
import 'framework.dart';
|
||||
import 'heroes.dart';
|
||||
import 'overlay.dart';
|
||||
import 'route_notification_messages.dart';
|
||||
import 'routes.dart';
|
||||
import 'ticker_provider.dart';
|
||||
|
||||
@ -3308,8 +3307,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
||||
_RouteEntry.isPresentPredicate, orElse: () => null);
|
||||
final String routeName = lastEntry?.route?.settings?.name;
|
||||
if (routeName != _lastAnnouncedRouteName) {
|
||||
RouteNotificationMessages.maybeNotifyRouteChange(
|
||||
routeName, _lastAnnouncedRouteName);
|
||||
SystemNavigator.routeUpdated(
|
||||
routeName: routeName,
|
||||
previousRouteName: _lastAnnouncedRouteName
|
||||
);
|
||||
_lastAnnouncedRouteName = routeName;
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Messages for route change notifications.
|
||||
class RouteNotificationMessages {
|
||||
// This class is not meant to be instantiated or extended; this constructor
|
||||
// prevents instantiation and extension.
|
||||
// ignore: unused_element
|
||||
RouteNotificationMessages._();
|
||||
|
||||
/// When the engine is Web notify the platform for a route change.
|
||||
static void maybeNotifyRouteChange(String routeName, String previousRouteName) {
|
||||
if(kIsWeb) {
|
||||
_notifyRouteChange(routeName, previousRouteName);
|
||||
} else {
|
||||
// No op.
|
||||
}
|
||||
}
|
||||
|
||||
/// Notifies the platform of a route change.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SystemChannels.navigation], which handles subsequent navigation
|
||||
/// requests.
|
||||
static void _notifyRouteChange(String routeName, String previousRouteName) {
|
||||
SystemChannels.navigation.invokeMethod<void>(
|
||||
'routeUpdated',
|
||||
<String, dynamic>{
|
||||
'previousRouteName': previousRouteName,
|
||||
'routeName': routeName,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
1248
packages/flutter/lib/src/widgets/router.dart
Normal file
1248
packages/flutter/lib/src/widgets/router.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -85,6 +85,7 @@ export 'src/widgets/primary_scroll_controller.dart';
|
||||
export 'src/widgets/raw_keyboard_listener.dart';
|
||||
export 'src/widgets/restoration.dart';
|
||||
export 'src/widgets/restoration_properties.dart';
|
||||
export 'src/widgets/router.dart';
|
||||
export 'src/widgets/routes.dart';
|
||||
export 'src/widgets/safe_area.dart';
|
||||
export 'src/widgets/scroll_activity.dart';
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Heroes work', (WidgetTester tester) async {
|
||||
@ -147,4 +149,101 @@ void main() {
|
||||
expect(key2.currentState, isA<NavigatorState>());
|
||||
expect(key1.currentState, isNull);
|
||||
});
|
||||
|
||||
testWidgets('CupertinoApp.router works', (WidgetTester tester) async {
|
||||
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
|
||||
initialRouteInformation: const RouteInformation(
|
||||
location: 'initial',
|
||||
),
|
||||
);
|
||||
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
},
|
||||
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
|
||||
delegate.routeInformation = const RouteInformation(
|
||||
location: 'popped',
|
||||
);
|
||||
return route.didPop(result);
|
||||
}
|
||||
);
|
||||
await tester.pumpWidget(CupertinoApp.router(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// Simulate android back button intent.
|
||||
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('popped'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
|
||||
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result, SimpleNavigatorRouterDelegate delegate);
|
||||
|
||||
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||
SimpleRouteInformationParser();
|
||||
|
||||
@override
|
||||
Future<RouteInformation> parseRouteInformation(RouteInformation information) {
|
||||
return SynchronousFuture<RouteInformation>(information);
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(RouteInformation configuration) {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> with PopNavigatorRouterDelegateMixin<RouteInformation>, ChangeNotifier {
|
||||
SimpleNavigatorRouterDelegate({
|
||||
@required this.builder,
|
||||
this.onPopPage,
|
||||
});
|
||||
|
||||
@override
|
||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
RouteInformation get routeInformation => _routeInformation;
|
||||
RouteInformation _routeInformation;
|
||||
set routeInformation(RouteInformation newValue) {
|
||||
_routeInformation = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SimpleRouterDelegateBuilder builder;
|
||||
SimpleNavigatorRouterDelegatePopPage<void> onPopPage;
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(RouteInformation configuration) {
|
||||
_routeInformation = configuration;
|
||||
return SynchronousFuture<void>(null);
|
||||
}
|
||||
|
||||
bool _handlePopPage(Route<void> route, void data) {
|
||||
return onPopPage(route, data, this);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
key: navigatorKey,
|
||||
onPopPage: _handlePopPage,
|
||||
pages: <Page<void>>[
|
||||
// We need at least two pages for the pop to propagate through.
|
||||
// Otherwise, the navigator will bubble the pop to the system navigator.
|
||||
CupertinoPage<void>(
|
||||
builder: (BuildContext context) => const Text('base'),
|
||||
),
|
||||
CupertinoPage<void>(
|
||||
key: ValueKey<String>(routeInformation?.location),
|
||||
builder: (BuildContext context) => builder(context, routeInformation),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/semantics.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -947,6 +949,37 @@ void main() {
|
||||
expect(key2.currentState, isA<NavigatorState>());
|
||||
expect(key1.currentState, isNull);
|
||||
});
|
||||
|
||||
testWidgets('MaterialApp.router works', (WidgetTester tester) async {
|
||||
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
|
||||
initialRouteInformation: const RouteInformation(
|
||||
location: 'initial',
|
||||
),
|
||||
);
|
||||
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
},
|
||||
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
|
||||
delegate.routeInformation = const RouteInformation(
|
||||
location: 'popped',
|
||||
);
|
||||
return route.didPop(result);
|
||||
}
|
||||
);
|
||||
await tester.pumpWidget(MaterialApp.router(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// Simulate android back button intent.
|
||||
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('popped'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
class MockAccessibilityFeature implements AccessibilityFeatures {
|
||||
@ -968,3 +1001,69 @@ class MockAccessibilityFeature implements AccessibilityFeatures {
|
||||
@override
|
||||
bool get reduceMotion => true;
|
||||
}
|
||||
|
||||
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
|
||||
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result, SimpleNavigatorRouterDelegate delegate);
|
||||
|
||||
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||
SimpleRouteInformationParser();
|
||||
|
||||
@override
|
||||
Future<RouteInformation> parseRouteInformation(RouteInformation information) {
|
||||
return SynchronousFuture<RouteInformation>(information);
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(RouteInformation configuration) {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> with PopNavigatorRouterDelegateMixin<RouteInformation>, ChangeNotifier {
|
||||
SimpleNavigatorRouterDelegate({
|
||||
@required this.builder,
|
||||
this.onPopPage,
|
||||
});
|
||||
|
||||
@override
|
||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
RouteInformation get routeInformation => _routeInformation;
|
||||
RouteInformation _routeInformation;
|
||||
set routeInformation(RouteInformation newValue) {
|
||||
_routeInformation = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SimpleRouterDelegateBuilder builder;
|
||||
SimpleNavigatorRouterDelegatePopPage<void> onPopPage;
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(RouteInformation configuration) {
|
||||
_routeInformation = configuration;
|
||||
return SynchronousFuture<void>(null);
|
||||
}
|
||||
|
||||
bool _handlePopPage(Route<void> route, void data) {
|
||||
return onPopPage(route, data, this);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
key: navigatorKey,
|
||||
onPopPage: _handlePopPage,
|
||||
pages: <Page<void>>[
|
||||
// We need at least two pages for the pop to propagate through.
|
||||
// Otherwise, the navigator will bubble the pop to the system navigator.
|
||||
MaterialPage<void>(
|
||||
builder: (BuildContext context) => const Text('base'),
|
||||
),
|
||||
MaterialPage<void>(
|
||||
key: ValueKey<String>(routeInformation?.location),
|
||||
builder: (BuildContext context) => builder(context, routeInformation),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -264,4 +264,116 @@ void main() {
|
||||
expect(find.text('non-regular page one'), findsOneWidget);
|
||||
expect(find.text('regular page'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp.router works', (WidgetTester tester) async {
|
||||
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
|
||||
initialRouteInformation: const RouteInformation(
|
||||
location: 'initial',
|
||||
),
|
||||
);
|
||||
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
},
|
||||
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
|
||||
delegate.routeInformation = const RouteInformation(
|
||||
location: 'popped',
|
||||
);
|
||||
return route.didPop(result);
|
||||
}
|
||||
);
|
||||
await tester.pumpWidget(WidgetsApp.router(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
color: const Color(0xFF123456),
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// Simulate android back button intent.
|
||||
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('popped'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp.router has correct default', (WidgetTester tester) async {
|
||||
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
},
|
||||
);
|
||||
await tester.pumpWidget(WidgetsApp.router(
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
color: const Color(0xFF123456),
|
||||
));
|
||||
expect(find.text('/'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
|
||||
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result, SimpleNavigatorRouterDelegate delegate);
|
||||
|
||||
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||
SimpleRouteInformationParser();
|
||||
|
||||
@override
|
||||
Future<RouteInformation> parseRouteInformation(RouteInformation information) {
|
||||
return SynchronousFuture<RouteInformation>(information);
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(RouteInformation configuration) {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> with PopNavigatorRouterDelegateMixin<RouteInformation>, ChangeNotifier {
|
||||
SimpleNavigatorRouterDelegate({
|
||||
@required this.builder,
|
||||
this.onPopPage,
|
||||
});
|
||||
|
||||
@override
|
||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
RouteInformation get routeInformation => _routeInformation;
|
||||
RouteInformation _routeInformation;
|
||||
set routeInformation(RouteInformation newValue) {
|
||||
_routeInformation = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SimpleRouterDelegateBuilder builder;
|
||||
SimpleNavigatorRouterDelegatePopPage<void> onPopPage;
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(RouteInformation configuration) {
|
||||
_routeInformation = configuration;
|
||||
return SynchronousFuture<void>(null);
|
||||
}
|
||||
|
||||
bool _handlePopPage(Route<void> route, void data) {
|
||||
return onPopPage(route, data, this);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
key: navigatorKey,
|
||||
onPopPage: _handlePopPage,
|
||||
pages: <Page<void>>[
|
||||
// We need at least two pages for the pop to propagate through.
|
||||
// Otherwise, the navigator will bubble the pop to the system navigator.
|
||||
MaterialPage<void>(
|
||||
builder: (BuildContext context) => const Text('base'),
|
||||
),
|
||||
MaterialPage<void>(
|
||||
key: ValueKey<String>(routeInformation?.location),
|
||||
builder: (BuildContext context) => builder(context, routeInformation),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,16 @@ class PushRouteObserver with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
class PushRouteInformationObserver with WidgetsBindingObserver {
|
||||
RouteInformation pushedRouteInformation;
|
||||
|
||||
@override
|
||||
Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
|
||||
pushedRouteInformation = routeInformation;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -90,6 +100,38 @@ void main() {
|
||||
WidgetsBinding.instance.removeObserver(observer);
|
||||
});
|
||||
|
||||
testWidgets('didPushRouteInformation calls didPushRoute by default', (WidgetTester tester) async {
|
||||
final PushRouteObserver observer = PushRouteObserver();
|
||||
WidgetsBinding.instance.addObserver(observer);
|
||||
|
||||
const Map<String, dynamic> testRouteInformation = <String, dynamic>{
|
||||
'location': 'testRouteName',
|
||||
'state': 'state',
|
||||
'restorationData': <dynamic, dynamic>{'test': 'config'}
|
||||
};
|
||||
final ByteData message = const JSONMethodCodec().encodeMethodCall(
|
||||
const MethodCall('pushRouteInformation', testRouteInformation));
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
|
||||
expect(observer.pushedRoute, 'testRouteName');
|
||||
WidgetsBinding.instance.removeObserver(observer);
|
||||
});
|
||||
|
||||
testWidgets('didPushRouteInformation callback', (WidgetTester tester) async {
|
||||
final PushRouteInformationObserver observer = PushRouteInformationObserver();
|
||||
WidgetsBinding.instance.addObserver(observer);
|
||||
|
||||
const Map<String, dynamic> testRouteInformation = <String, dynamic>{
|
||||
'location': 'testRouteName',
|
||||
'state': 'state',
|
||||
};
|
||||
final ByteData message = const JSONMethodCodec().encodeMethodCall(
|
||||
const MethodCall('pushRouteInformation', testRouteInformation));
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
|
||||
expect(observer.pushedRouteInformation.location, 'testRouteName');
|
||||
expect(observer.pushedRouteInformation.state, 'state');
|
||||
WidgetsBinding.instance.removeObserver(observer);
|
||||
});
|
||||
|
||||
testWidgets('Application lifecycle affects frame scheduling', (WidgetTester tester) async {
|
||||
final BinaryMessenger defaultBinaryMessenger = ServicesBinding.instance.defaultBinaryMessenger;
|
||||
ByteData message;
|
||||
|
@ -36,6 +36,13 @@ class OnTapPage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> convertRouteInformationToMap(RouteInformation routeInformation) {
|
||||
return <String, dynamic>{
|
||||
'location': routeInformation.location,
|
||||
'state': routeInformation.state,
|
||||
};
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('Push and Pop should send platform messages', (WidgetTester tester) async {
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
@ -258,4 +265,109 @@ void main() {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('PlatformRouteInformationProvider reports URL', (WidgetTester tester) async {
|
||||
final List<MethodCall> log = <MethodCall>[];
|
||||
SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||
log.add(methodCall);
|
||||
});
|
||||
|
||||
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
|
||||
initialRouteInformation: const RouteInformation(
|
||||
location: 'initial',
|
||||
),
|
||||
);
|
||||
final SimpleRouterDelegate delegate = SimpleRouterDelegate(
|
||||
reportConfiguration: true,
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
}
|
||||
);
|
||||
|
||||
await tester.pumpWidget(MaterialApp.router(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// Triggers a router rebuild and verify the route information is reported
|
||||
// to the web engine.
|
||||
delegate.routeInformation = const RouteInformation(
|
||||
location: 'update',
|
||||
state: 'state',
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.text('update'), findsOneWidget);
|
||||
|
||||
expect(log, hasLength(1));
|
||||
// TODO(chunhtai): check routeInformationUpdated instead once the engine
|
||||
// side is done.
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
|
||||
'location': 'update',
|
||||
'state': 'state',
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
|
||||
typedef SimpleRouterDelegatePopRoute = Future<bool> Function();
|
||||
|
||||
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||
SimpleRouteInformationParser();
|
||||
|
||||
@override
|
||||
Future<RouteInformation> parseRouteInformation(RouteInformation information) {
|
||||
return SynchronousFuture<RouteInformation>(information);
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(RouteInformation configuration) {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier {
|
||||
SimpleRouterDelegate({
|
||||
@required this.builder,
|
||||
this.onPopRoute,
|
||||
this.reportConfiguration = false,
|
||||
});
|
||||
|
||||
RouteInformation get routeInformation => _routeInformation;
|
||||
RouteInformation _routeInformation;
|
||||
set routeInformation(RouteInformation newValue) {
|
||||
_routeInformation = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SimpleRouterDelegateBuilder builder;
|
||||
SimpleRouterDelegatePopRoute onPopRoute;
|
||||
final bool reportConfiguration;
|
||||
|
||||
@override
|
||||
RouteInformation get currentConfiguration {
|
||||
if (reportConfiguration)
|
||||
return routeInformation;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(RouteInformation configuration) {
|
||||
_routeInformation = configuration;
|
||||
return SynchronousFuture<void>(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> popRoute() {
|
||||
if (onPopRoute != null)
|
||||
return onPopRoute();
|
||||
return SynchronousFuture<bool>(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => builder(context, routeInformation);
|
||||
}
|
||||
|
705
packages/flutter/test/widgets/router_test.dart
Normal file
705
packages/flutter/test/widgets/router_test.dart
Normal file
@ -0,0 +1,705 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Simple router basic functionality - synchronized', (WidgetTester tester) async {
|
||||
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||
provider.value = const RouteInformation(
|
||||
location: 'initial',
|
||||
);
|
||||
await tester.pumpWidget(buildBoilerPlate(
|
||||
Router<RouteInformation>(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: SimpleRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
}
|
||||
),
|
||||
)
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
provider.value = const RouteInformation(
|
||||
location: 'update',
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.text('initial'), findsNothing);
|
||||
expect(find.text('update'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Simple router basic functionality - asynchronized', (WidgetTester tester) async {
|
||||
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||
provider.value = const RouteInformation(
|
||||
location: 'initial',
|
||||
);
|
||||
final SimpleAsyncRouteInformationParser parser = SimpleAsyncRouteInformationParser();
|
||||
final SimpleAsyncRouterDelegate delegate = SimpleAsyncRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
if (information == null)
|
||||
return const Text('waiting');
|
||||
return Text(information.location);
|
||||
}
|
||||
);
|
||||
await tester.runAsync(() async {
|
||||
await tester.pumpWidget(buildBoilerPlate(
|
||||
Router<RouteInformation>(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: parser,
|
||||
routerDelegate: delegate,
|
||||
)
|
||||
));
|
||||
// Future has not yet completed.
|
||||
expect(find.text('waiting'), findsOneWidget);
|
||||
|
||||
await parser.parsingFuture;
|
||||
await delegate.setNewRouteFuture;
|
||||
await tester.pump();
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
provider.value = const RouteInformation(
|
||||
location: 'update',
|
||||
);
|
||||
await tester.pump();
|
||||
// Future has not yet completed.
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
await parser.parsingFuture;
|
||||
await delegate.setNewRouteFuture;
|
||||
await tester.pump();
|
||||
expect(find.text('update'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Simple router can handle pop route', (WidgetTester tester) async {
|
||||
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||
provider.value = const RouteInformation(
|
||||
location: 'initial',
|
||||
);
|
||||
final BackButtonDispatcher dispatcher = RootBackButtonDispatcher();
|
||||
|
||||
await tester.pumpWidget(buildBoilerPlate(
|
||||
Router<RouteInformation>(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: SimpleRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
},
|
||||
onPopRoute: () {
|
||||
provider.value = const RouteInformation(
|
||||
location: 'popped',
|
||||
);
|
||||
return SynchronousFuture<bool>(true);
|
||||
}
|
||||
),
|
||||
backButtonDispatcher: dispatcher,
|
||||
)
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
bool result = false;
|
||||
// SynchronousFuture should complete immediately.
|
||||
dispatcher.invokeCallback(SynchronousFuture<bool>(false))
|
||||
.then((bool data) {
|
||||
result = data;
|
||||
});
|
||||
expect(result, isTrue);
|
||||
|
||||
await tester.pump();
|
||||
expect(find.text('popped'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('PopNavigatorRouterDelegateMixin works', (WidgetTester tester) async {
|
||||
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||
provider.value = const RouteInformation(
|
||||
location: 'initial',
|
||||
);
|
||||
final BackButtonDispatcher dispatcher = RootBackButtonDispatcher();
|
||||
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
},
|
||||
onPopPage: (Route<void> route, void result) {
|
||||
provider.value = const RouteInformation(
|
||||
location: 'popped',
|
||||
);
|
||||
return route.didPop(result);
|
||||
}
|
||||
);
|
||||
await tester.pumpWidget(buildBoilerPlate(
|
||||
Router<RouteInformation>(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
backButtonDispatcher: dispatcher,
|
||||
)
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// Pushes a nameless route.
|
||||
showDialog<void>(
|
||||
useRootNavigator: false,
|
||||
context: delegate.navigatorKey.currentContext,
|
||||
builder: (BuildContext context) => const Text('dialog')
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('dialog'), findsOneWidget);
|
||||
|
||||
// Pops the nameless route and makes sure the initial page is shown.
|
||||
bool result = false;
|
||||
result = await dispatcher.invokeCallback(SynchronousFuture<bool>(false));
|
||||
expect(result, isTrue);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
expect(find.text('dialog'), findsNothing);
|
||||
|
||||
// Pops one more time.
|
||||
result = false;
|
||||
result = await dispatcher.invokeCallback(SynchronousFuture<bool>(false));
|
||||
expect(result, isTrue);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('popped'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Nested routers back button dispatcher works', (WidgetTester tester) async {
|
||||
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||
provider.value = const RouteInformation(
|
||||
location: 'initial',
|
||||
);
|
||||
final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher();
|
||||
await tester.pumpWidget(buildBoilerPlate(
|
||||
Router<RouteInformation>(
|
||||
backButtonDispatcher: outerDispatcher,
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: SimpleRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
final BackButtonDispatcher innerDispatcher = ChildBackButtonDispatcher(outerDispatcher);
|
||||
innerDispatcher.takePriority();
|
||||
// Creates the sub-router.
|
||||
return Router<RouteInformation>(
|
||||
backButtonDispatcher: innerDispatcher,
|
||||
routerDelegate: SimpleRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation innerInformation) {
|
||||
return Text(information.location);
|
||||
},
|
||||
onPopRoute: () {
|
||||
provider.value = const RouteInformation(
|
||||
location: 'popped inner',
|
||||
);
|
||||
return SynchronousFuture<bool>(true);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
onPopRoute: () {
|
||||
provider.value = const RouteInformation(
|
||||
location: 'popped outter',
|
||||
);
|
||||
return SynchronousFuture<bool>(true);
|
||||
}
|
||||
),
|
||||
)
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// The outer dispatcher should trigger the pop on the inner router.
|
||||
bool result = false;
|
||||
result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false));
|
||||
expect(result, isTrue);
|
||||
await tester.pump();
|
||||
expect(find.text('popped inner'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Nested router back button dispatcher works for multiple children', (WidgetTester tester) async {
|
||||
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
|
||||
provider.value = const RouteInformation(
|
||||
location: 'initial',
|
||||
);
|
||||
final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher();
|
||||
final BackButtonDispatcher innerDispatcher1 = ChildBackButtonDispatcher(outerDispatcher);
|
||||
final BackButtonDispatcher innerDispatcher2 = ChildBackButtonDispatcher(outerDispatcher);
|
||||
await tester.pumpWidget(buildBoilerPlate(
|
||||
Router<RouteInformation>(
|
||||
backButtonDispatcher: outerDispatcher,
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: SimpleRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
// Creates the sub-router.
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Text(information.location),
|
||||
Router<RouteInformation>(
|
||||
backButtonDispatcher: innerDispatcher1,
|
||||
routerDelegate: SimpleRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation innerInformation) {
|
||||
return Container();
|
||||
},
|
||||
onPopRoute: () {
|
||||
provider.value = const RouteInformation(
|
||||
location: 'popped inner1',
|
||||
);
|
||||
return SynchronousFuture<bool>(true);
|
||||
},
|
||||
),
|
||||
),
|
||||
Router<RouteInformation>(
|
||||
backButtonDispatcher: innerDispatcher2,
|
||||
routerDelegate: SimpleRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation innerInformation) {
|
||||
return Container();
|
||||
},
|
||||
onPopRoute: () {
|
||||
provider.value = const RouteInformation(
|
||||
location: 'popped inner2',
|
||||
);
|
||||
return SynchronousFuture<bool>(true);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
onPopRoute: () {
|
||||
provider.value = const RouteInformation(
|
||||
location: 'popped outter',
|
||||
);
|
||||
return SynchronousFuture<bool>(true);
|
||||
}
|
||||
),
|
||||
)
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// If none of the children have taken the priority, the root router handles
|
||||
// the pop.
|
||||
bool result = false;
|
||||
result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false));
|
||||
expect(result, isTrue);
|
||||
await tester.pump();
|
||||
expect(find.text('popped outter'), findsOneWidget);
|
||||
|
||||
innerDispatcher1.takePriority();
|
||||
result = false;
|
||||
result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false));
|
||||
expect(result, isTrue);
|
||||
await tester.pump();
|
||||
expect(find.text('popped inner1'), findsOneWidget);
|
||||
|
||||
// The last child dispatcher that took priority handles the pop.
|
||||
innerDispatcher2.takePriority();
|
||||
result = false;
|
||||
result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false));
|
||||
expect(result, isTrue);
|
||||
await tester.pump();
|
||||
expect(find.text('popped inner2'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('router does report URL change correctly', (WidgetTester tester) async {
|
||||
RouteInformation reportedRouteInformation;
|
||||
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(
|
||||
onRouterReport: (RouteInformation information) {
|
||||
// Makes sure we only report once after manually cleaning up.
|
||||
expect(reportedRouteInformation, isNull);
|
||||
reportedRouteInformation = information;
|
||||
}
|
||||
);
|
||||
final SimpleRouterDelegate delegate = SimpleRouterDelegate(
|
||||
reportConfiguration: true,
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
}
|
||||
);
|
||||
delegate.onPopRoute = () {
|
||||
delegate.routeInformation = const RouteInformation(
|
||||
location: 'popped',
|
||||
);
|
||||
return SynchronousFuture<bool>(true);
|
||||
};
|
||||
final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher();
|
||||
|
||||
provider.value = const RouteInformation(
|
||||
location: 'initial',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildBoilerPlate(
|
||||
Router<RouteInformation>(
|
||||
backButtonDispatcher: outerDispatcher,
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
)
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
expect(reportedRouteInformation, isNull);
|
||||
delegate.routeInformation = const RouteInformation(
|
||||
location: 'update',
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.text('initial'), findsNothing);
|
||||
expect(find.text('update'), findsOneWidget);
|
||||
expect(reportedRouteInformation.location, 'update');
|
||||
|
||||
// The router should not report if only state changes.
|
||||
reportedRouteInformation = null;
|
||||
delegate.routeInformation = const RouteInformation(
|
||||
location: 'update',
|
||||
state: 'another state',
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.text('update'), findsOneWidget);
|
||||
expect(reportedRouteInformation, isNull);
|
||||
|
||||
reportedRouteInformation = null;
|
||||
bool result = false;
|
||||
result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false));
|
||||
expect(result, isTrue);
|
||||
await tester.pump();
|
||||
expect(find.text('popped'), findsOneWidget);
|
||||
expect(reportedRouteInformation.location, 'popped');
|
||||
});
|
||||
|
||||
testWidgets('router can be forced to recognize or ignore navigating events', (WidgetTester tester) async {
|
||||
RouteInformation reportedRouteInformation;
|
||||
bool isNavigating = false;
|
||||
RouteInformation nextRouteInformation;
|
||||
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(
|
||||
onRouterReport: (RouteInformation information) {
|
||||
// Makes sure we only report once after manually cleaning up.
|
||||
expect(reportedRouteInformation, isNull);
|
||||
reportedRouteInformation = information;
|
||||
}
|
||||
);
|
||||
provider.value = const RouteInformation(
|
||||
location: 'initial',
|
||||
);
|
||||
final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true);
|
||||
delegate.builder = (BuildContext context, RouteInformation information) {
|
||||
return ElevatedButton(
|
||||
child: Text(information.location),
|
||||
onPressed: () {
|
||||
if (isNavigating) {
|
||||
Router.navigate(context, () {
|
||||
if (delegate.routeInformation != nextRouteInformation)
|
||||
delegate.routeInformation = nextRouteInformation;
|
||||
});
|
||||
} else {
|
||||
Router.neglect(context, () {
|
||||
if (delegate.routeInformation != nextRouteInformation)
|
||||
delegate.routeInformation = nextRouteInformation;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher();
|
||||
|
||||
await tester.pumpWidget(buildBoilerPlate(
|
||||
Router<RouteInformation>(
|
||||
backButtonDispatcher: outerDispatcher,
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
)
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
expect(reportedRouteInformation, isNull);
|
||||
|
||||
nextRouteInformation = const RouteInformation(
|
||||
location: 'update',
|
||||
);
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pump();
|
||||
expect(find.text('initial'), findsNothing);
|
||||
expect(find.text('update'), findsOneWidget);
|
||||
expect(reportedRouteInformation, isNull);
|
||||
|
||||
isNavigating = true;
|
||||
// This should not trigger any real navigating event because the
|
||||
// nextRouteInformation does not change. However, the router should still
|
||||
// report a route information because isNavigating = true.
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pump();
|
||||
expect(reportedRouteInformation.location, 'update');
|
||||
});
|
||||
|
||||
testWidgets('PlatformRouteInformationProvider works', (WidgetTester tester) async {
|
||||
final RouteInformationProvider provider = PlatformRouteInformationProvider(
|
||||
initialRouteInformation: const RouteInformation(
|
||||
location: 'initial',
|
||||
),
|
||||
);
|
||||
final SimpleRouterDelegate delegate = SimpleRouterDelegate(
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
final List<Widget> children = <Widget>[];
|
||||
if (information.location != null)
|
||||
children.add(Text(information.location));
|
||||
if (information.state != null)
|
||||
children.add(Text(information.state.toString()));
|
||||
return Column(
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
await tester.pumpWidget(MaterialApp.router(
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// Pushes through the `pushRouteInformation` in the navigation method channel.
|
||||
const Map<String, dynamic> testRouteInformation = <String, dynamic>{
|
||||
'location': 'testRouteName',
|
||||
'state': 'state',
|
||||
};
|
||||
final ByteData routerMessage = const JSONMethodCodec().encodeMethodCall(
|
||||
const MethodCall('pushRouteInformation', testRouteInformation)
|
||||
);
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', routerMessage, (_) { });
|
||||
await tester.pump();
|
||||
expect(find.text('testRouteName'), findsOneWidget);
|
||||
expect(find.text('state'), findsOneWidget);
|
||||
|
||||
// Pushes through the `pushRoute` in the navigation method channel.
|
||||
const String testRouteName = 'newTestRouteName';
|
||||
final ByteData message = const JSONMethodCodec().encodeMethodCall(
|
||||
const MethodCall('pushRoute', testRouteName));
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
|
||||
await tester.pump();
|
||||
expect(find.text('newTestRouteName'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('RootBackButtonDispatcher works', (WidgetTester tester) async {
|
||||
final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher();
|
||||
final RouteInformationProvider provider = PlatformRouteInformationProvider(
|
||||
initialRouteInformation: const RouteInformation(
|
||||
location: 'initial',
|
||||
),
|
||||
);
|
||||
final SimpleRouterDelegate delegate = SimpleRouterDelegate(
|
||||
reportConfiguration: true,
|
||||
builder: (BuildContext context, RouteInformation information) {
|
||||
return Text(information.location);
|
||||
}
|
||||
);
|
||||
delegate.onPopRoute = () {
|
||||
delegate.routeInformation = const RouteInformation(
|
||||
location: 'popped',
|
||||
);
|
||||
return SynchronousFuture<bool>(true);
|
||||
};
|
||||
|
||||
await tester.pumpWidget(MaterialApp.router(
|
||||
backButtonDispatcher: outerDispatcher,
|
||||
routeInformationProvider: provider,
|
||||
routeInformationParser: SimpleRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
));
|
||||
expect(find.text('initial'), findsOneWidget);
|
||||
|
||||
// Pop route through the message channel.
|
||||
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
|
||||
await tester.pump();
|
||||
expect(find.text('popped'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildBoilerPlate(Widget child) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
|
||||
typedef SimpleRouterDelegatePopRoute = Future<bool> Function();
|
||||
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result);
|
||||
typedef RouterReportRouterInformation = void Function(RouteInformation);
|
||||
|
||||
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||
SimpleRouteInformationParser();
|
||||
|
||||
@override
|
||||
Future<RouteInformation> parseRouteInformation(RouteInformation information) {
|
||||
return SynchronousFuture<RouteInformation>(information);
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(RouteInformation configuration) {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier {
|
||||
SimpleRouterDelegate({
|
||||
this.builder,
|
||||
this.onPopRoute,
|
||||
this.reportConfiguration = false,
|
||||
});
|
||||
|
||||
RouteInformation get routeInformation => _routeInformation;
|
||||
RouteInformation _routeInformation;
|
||||
set routeInformation(RouteInformation newValue) {
|
||||
_routeInformation = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SimpleRouterDelegateBuilder builder;
|
||||
SimpleRouterDelegatePopRoute onPopRoute;
|
||||
final bool reportConfiguration;
|
||||
|
||||
@override
|
||||
RouteInformation get currentConfiguration {
|
||||
if (reportConfiguration)
|
||||
return routeInformation;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(RouteInformation configuration) {
|
||||
_routeInformation = configuration;
|
||||
return SynchronousFuture<void>(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> popRoute() {
|
||||
if (onPopRoute != null)
|
||||
return onPopRoute();
|
||||
return SynchronousFuture<bool>(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => builder(context, routeInformation);
|
||||
}
|
||||
|
||||
class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> with PopNavigatorRouterDelegateMixin<RouteInformation>, ChangeNotifier {
|
||||
SimpleNavigatorRouterDelegate({
|
||||
@required this.builder,
|
||||
this.onPopPage,
|
||||
});
|
||||
|
||||
@override
|
||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
RouteInformation get routeInformation => _routeInformation;
|
||||
RouteInformation _routeInformation;
|
||||
set routeInformation(RouteInformation newValue) {
|
||||
_routeInformation = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SimpleRouterDelegateBuilder builder;
|
||||
SimpleNavigatorRouterDelegatePopPage<void> onPopPage;
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(RouteInformation configuration) {
|
||||
_routeInformation = configuration;
|
||||
return SynchronousFuture<void>(null);
|
||||
}
|
||||
|
||||
bool _handlePopPage(Route<void> route, void data) {
|
||||
return onPopPage(route, data);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
key: navigatorKey,
|
||||
onPopPage: _handlePopPage,
|
||||
pages: <Page<void>>[
|
||||
// We need at least two pages for the pop to propagate through.
|
||||
// Otherwise, the navigator will bubble the pop to the system navigator.
|
||||
MaterialPage<void>(
|
||||
builder: (BuildContext context) => const Text('base'),
|
||||
),
|
||||
MaterialPage<void>(
|
||||
key: ValueKey<String>(routeInformation?.location),
|
||||
builder: (BuildContext context) => builder(context, routeInformation),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleRouteInformationProvider extends RouteInformationProvider with ChangeNotifier {
|
||||
SimpleRouteInformationProvider({
|
||||
this.onRouterReport
|
||||
});
|
||||
|
||||
RouterReportRouterInformation onRouterReport;
|
||||
|
||||
@override
|
||||
RouteInformation get value => _value;
|
||||
RouteInformation _value;
|
||||
set value(RouteInformation newValue) {
|
||||
_value = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void routerReportsNewRouteInformation(RouteInformation routeInformation) {
|
||||
if (onRouterReport != null)
|
||||
onRouterReport(routeInformation);
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleAsyncRouteInformationParser extends RouteInformationParser<RouteInformation> {
|
||||
SimpleAsyncRouteInformationParser();
|
||||
|
||||
Future<RouteInformation> parsingFuture;
|
||||
|
||||
@override
|
||||
Future<RouteInformation> parseRouteInformation(RouteInformation information) {
|
||||
return parsingFuture = Future<RouteInformation>.value(information);
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(RouteInformation configuration) {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleAsyncRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier{
|
||||
SimpleAsyncRouterDelegate({
|
||||
@required this.builder,
|
||||
});
|
||||
|
||||
RouteInformation get routeInformation => _routeInformation;
|
||||
RouteInformation _routeInformation;
|
||||
set routeInformation(RouteInformation newValue) {
|
||||
_routeInformation = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SimpleRouterDelegateBuilder builder;
|
||||
Future<void> setNewRouteFuture;
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(RouteInformation configuration) {
|
||||
_routeInformation = configuration;
|
||||
return setNewRouteFuture = Future<void>.value();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> popRoute() {
|
||||
return Future<bool>.value(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => builder(context, routeInformation);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user