Skip to content

Commit c1064da

Browse files
authored
Fixes Router transaction to respect operation order (#149763)
fixes flutter/flutter#142393 The issue is that if routerdelegate mutate its currentConfiguration outside of Router's workflow, the change will be propagate back to routeinformationprovider at the end of the frame. However if another things trigger router's dependencies change within the same frame. The dependencies will trigger a reparse of the current route which would end up override the currentConfiguration in routerDelegate. This change introduce a transaction system that each operation will be add to the end of the transaction future so everything will be execute inorder
1 parent ff0ab6b commit c1064da

File tree

2 files changed

+81
-2
lines changed

2 files changed

+81
-2
lines changed

packages/flutter/lib/src/widgets/router.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,8 +694,9 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
694694
// The super.didChangeDependencies may have parsed the route information.
695695
// This can happen if the didChangeDependencies is triggered by state
696696
// restoration or first build.
697-
if (widget.routeInformationProvider != null && _routeParsePending) {
698-
_processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
697+
final RouteInformation? currentRouteInformation = _routeInformation.value ?? widget.routeInformationProvider?.value;
698+
if (currentRouteInformation != null && _routeParsePending) {
699+
_processRouteInformation(currentRouteInformation, () => widget.routerDelegate.setNewRoutePath);
699700
}
700701
_routeParsePending = false;
701702
_maybeNeedToReportRouteInformation();

packages/flutter/test/widgets/router_test.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,44 @@ void main() {
4141
expect(find.text('update'), findsOneWidget);
4242
});
4343

44+
testWidgets('Router respects update order', (WidgetTester tester) async {
45+
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
46+
addTearDown(provider.dispose);
47+
provider.value = RouteInformation(
48+
uri: Uri.parse('initial'),
49+
);
50+
51+
final MutableRouterDelegate delegate = MutableRouterDelegate();
52+
addTearDown(delegate.dispose);
53+
54+
final ValueNotifier<int> notifier = ValueNotifier<int>(0);
55+
await tester.pumpWidget(buildBoilerPlate(
56+
IntInheritedNotifier(
57+
notifier: notifier,
58+
child: Router<RouteInformation>(
59+
routeInformationProvider: provider,
60+
routeInformationParser: CustomRouteInformationParser(
61+
(RouteInformation information, BuildContext context) {
62+
IntInheritedNotifier.of(context); // create dependency
63+
return information;
64+
},
65+
),
66+
routerDelegate: delegate,
67+
),
68+
)
69+
));
70+
expect(find.text('initial'), findsOneWidget);
71+
expect(delegate.currentConfiguration!.uri.toString(), 'initial');
72+
73+
delegate.updateConfiguration(RouteInformation(uri: Uri.parse('update')));
74+
notifier.value = 1;
75+
76+
// The delegate should still retain the update.
77+
await tester.pumpAndSettle();
78+
expect(find.text('update'), findsOneWidget);
79+
expect(delegate.currentConfiguration!.uri.toString(), 'update');
80+
});
81+
4482
testWidgets('Simple router basic functionality - asynchronized', (WidgetTester tester) async {
4583
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
4684
addTearDown(provider.dispose);
@@ -1934,3 +1972,43 @@ class RedirectingInformationParser extends RouteInformationParser<RouteInformati
19341972
return configuration;
19351973
}
19361974
}
1975+
1976+
class MutableRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier {
1977+
MutableRouterDelegate() {
1978+
if (kFlutterMemoryAllocationsEnabled) {
1979+
ChangeNotifier.maybeDispatchObjectCreation(this);
1980+
}
1981+
}
1982+
1983+
@override
1984+
RouteInformation? currentConfiguration;
1985+
1986+
@override
1987+
Future<void> setNewRoutePath(RouteInformation configuration) {
1988+
currentConfiguration = configuration;
1989+
return SynchronousFuture<void>(null);
1990+
}
1991+
1992+
void updateConfiguration(RouteInformation newConfig) {
1993+
currentConfiguration = newConfig;
1994+
notifyListeners();
1995+
}
1996+
1997+
@override
1998+
Future<bool> popRoute() {
1999+
throw UnimplementedError();
2000+
}
2001+
2002+
@override
2003+
Widget build(BuildContext context) {
2004+
return Text(currentConfiguration?.uri.toString() ?? '');
2005+
}
2006+
}
2007+
2008+
class IntInheritedNotifier extends InheritedNotifier<ValueListenable<int>> {
2009+
const IntInheritedNotifier({super.key, required super.notifier, required super.child});
2010+
2011+
static int of(BuildContext context) {
2012+
return context.dependOnInheritedWidgetOfExactType<IntInheritedNotifier>()!.notifier!.value;
2013+
}
2014+
}

0 commit comments

Comments
 (0)