-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Steps to Reproduce
- Execute
flutter runon the code sample (see "Code sample" section below) - Click on "Dialog" button to display the dialog
- Try to go to the dialog on the right
- Notice that the dialog doesn't change
Expected results:
I'm expecting the dialog to rebuild
Actual results:
It is not rebuilding.
I can force it to rebuild by changing the key ValueKey('dialog-$_selected'), instead of ValueKey('dialog'),
But doing so prevents me from reusing states.
You can see in the logs that it goes through the initState of _DialogWidgetState:
flutter: initState 1
flutter: initState 2
flutter: initState 3
flutter: initState 2
flutter: initState 1
I believe this might be coming from
flutter/packages/flutter/lib/src/widgets/routes.dart
Lines 1710 to 1717 in de4c0b1
| // We cache the part of the modal scope that doesn't change from frame to | |
| // frame so that we minimize the amount of building that happens. | |
| Widget? _modalScopeCache; | |
| // one of the builders | |
| Widget _buildModalScope(BuildContext context) { | |
| // To be sorted before the _modalBarrier. | |
| return _modalScopeCache ??= Semantics( |
and/or
flutter/packages/flutter/lib/src/widgets/routes.dart
Lines 817 to 821 in de4c0b1
| // We cache the result of calling the route's buildPage, and clear the cache | |
| // whenever the dependencies change. This implements the contract described in | |
| // the documentation for buildPage, namely that it gets called once, unless | |
| // something like a ModalRoute.of() dependency triggers an update. | |
| Widget? _page; |
As a comparison, I also included another example with the MaterialPage which works fine.
Code sample
Or you can check out https://github.com/ValentinVignal/flutter_app_stable/tree/flutter/dialog-being-cached
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class AppRoute {
const AppRoute(this.location);
final int? location;
}
class AppRouter extends RouterDelegate<AppRoute>
with PopNavigatorRouterDelegateMixin, ChangeNotifier {
// get correct state of router
@override
AppRoute get currentConfiguration => AppRoute(_selected);
int? _selected;
int get selected => _selected!;
set selected(int value) {
_selected = value == 0 ? null : value;
notifyListeners();
}
@override
Widget build(BuildContext context) {
return Navigator(
key: _navigatorKey,
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
_selected = null;
notifyListeners();
return true;
},
pages: [
const MaterialPage(
key: ValueKey('home'),
child: Home(),
),
if (_selected != null && _selected! > 0)
DialogPage(
key: const ValueKey('dialog'), // <- Replace with `ValueKey('dialog-$selected')`, and the UI rebuilds but the state of the dialog is lost. It will print `'iniState1'`, `'iniState2'`... in `_DialogWidgetState`'s `initState`.
child: DialogWidget(
parameter: _selected!,
),
)
else if (_selected != null && _selected! < 0)
MaterialPage(
key: const ValueKey('material'),
child: DialogWidget(
parameter: _selected!,
),
)
],
);
}
final _navigatorKey = GlobalKey<NavigatorState>();
@override
GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;
@override
Future<void> setNewRoutePath(AppRoute configuration) async {
_selected = configuration.location;
}
}
class AppRouteInformationParser extends RouteInformationParser<AppRoute> {
// This converts route state to route information.
@override
RouteInformation restoreRouteInformation(configuration) {
final String location;
if (configuration.location == null) {
location = '/';
} else {
location = '/dialog/${configuration.location}';
}
return RouteInformation(location: location);
}
// This converts route info to router state
@override
Future<AppRoute> parseRouteInformation(
RouteInformation routeInformation) async {
final int? selected;
if (routeInformation.location?.startsWith('/dialog/') ?? false) {
selected = int.parse(routeInformation.location!.split('/').last);
} else {
selected = null;
}
return AppRoute(selected);
}
}
final delegate = AppRouter();
final informationParser = AppRouteInformationParser();
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: delegate,
routeInformationParser: informationParser,
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () {
delegate.selected = 1;
},
child: const Text('Dialog'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
delegate.selected = -1;
},
child: const Text('Material'),
),
],
),
),
);
}
}
class DialogWidget extends StatefulWidget {
const DialogWidget({
required this.parameter,
super.key,
});
final int parameter;
@override
State<DialogWidget> createState() => _DialogWidgetState();
}
class _DialogWidgetState extends State<DialogWidget> {
@override
void initState() {
super.initState();
print('initState ${widget.parameter}');
}
@override
Widget build(BuildContext context) {
return SimpleDialog(
title: const Text('Dialog'),
children: [
Row(
children: [
IconButton(
onPressed: () {
delegate.selected--;
},
icon: const Icon(Icons.arrow_back_ios),
),
Text(widget.parameter.toString()),
IconButton(
onPressed: () {
delegate.selected++;
},
icon: const Icon(Icons.arrow_forward_ios),
),
],
),
],
);
}
}
class DialogPage extends Page {
const DialogPage({
required this.child,
super.key,
});
final Widget child;
@override
Route createRoute(BuildContext context) {
return _DialogRoute(
settings: this,
context: context,
builder: (context) {
ModalRoute.of(context);
return child;
},
);
}
}
class _DialogRoute extends DialogRoute {
_DialogRoute({
super.settings,
required super.context,
required super.builder,
});
@override
bool canTransitionFrom(TransitionRoute previousRoute) {
return previousRoute is! _DialogRoute;
}
@override
bool canTransitionTo(TransitionRoute nextRoute) {
return nextRoute is! _DialogRoute;
}
}Logs
For the logs, see the table in the "Videos" section.
Analyzing flutter_app_stable...
info • Avoid `print` calls in production code • lib/main.dart:158:5 • avoid_print
1 issue found. (ran in 5.0s)
[✓] Flutter (Channel stable, 3.3.8, on macOS 11.6.8 20G730 darwin-x64, locale en-GB)
• Flutter version 3.3.8 on channel stable at /Users/valentin/flutter/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 52b3dc25f6 (9 days ago), 2022-11-09 12:09:26 +0800
• Engine revision 857bd6b74c
• Dart version 2.18.4
• DevTools version 2.15.0
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
• Android SDK at /usr/local/Caskroom/android-sdk/4333796
• Platform android-33, build-tools 30.0.3
• Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 13C100
• CocoaPods version 1.11.3
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2021.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)
[✓] VS Code (version 1.73.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.52.0
[✓] Connected device (2 available)
• macOS (desktop) • macos • darwin-x64 • macOS 11.6.8 20G730 darwin-x64
• Chrome (web) • chrome • web-javascript • Google Chrome 107.0.5304.110
[✓] HTTP Host Availability
• All required HTTP hosts are available
• No issues found!
Videos
| With `const ValueKey('dialog')` as a key | With `ValueKey('dialog-$_selected')` as a key | |
|---|---|---|
| Video |
Screen.Recording.2022-11-18.at.5.52.20.PM.mov |
Screen.Recording.2022-11-18.at.6.02.10.PM.mov |
| Logs |
flutter run -d macos
Launching lib/main.dart on macOS in debug mode...
Building macOS application...
Syncing files to device macOS... 71ms
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
💪 Running with sound null safety 💪
An Observatory debugger and profiler on macOS is available at: http://127.0.0.1:62454/Sc8VIzL_xkw=/
The Flutter DevTools debugger and profiler on macOS is available at:
http://127.0.0.1:9102?uri=http://127.0.0.1:62454/Sc8VIzL_xkw=/
flutter: initState -1
flutter: initState -1
flutter: initState 1 |
Launching lib/main.dart on macOS in debug mode...
Building macOS application...
Syncing files to device macOS... 71ms
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
💪 Running with sound null safety 💪
An Observatory debugger and profiler on macOS is available at: http://127.0.0.1:63181/P2tBIa5DOV8=/
The Flutter DevTools debugger and profiler on macOS is available at:
http://127.0.0.1:9102?uri=http://127.0.0.1:63181/P2tBIa5DOV8=/
flutter: initState -1
flutter: initState 1
flutter: initState 2
flutter: initState 3
flutter: initState 2
flutter: initState 1 |