Unverified Commit 2a8e7b7b authored by chunhtai's avatar chunhtai Committed by GitHub

fix changinternalstate crash when remove local history entry in final… (#52561)

parent fed6887a
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'basic.dart';
import 'focus_manager.dart';
......@@ -606,9 +607,19 @@ mixin LocalHistoryRoute<T> on Route<T> {
_localHistory.remove(entry);
entry._owner = null;
entry._notifyRemoved();
if (_localHistory.isEmpty)
if (_localHistory.isEmpty) {
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
// The local history might be removed as a result of disposing inactive
// elements during finalizeTree. The state is locked at this moment, and
// we can only notify state has changed in the next frame.
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
changedInternalState();
});
} else {
changedInternalState();
}
}
}
@override
Future<RoutePopDisposition> willPop() async {
......
......@@ -1294,6 +1294,28 @@ void main() {
// It should refocus page one after pops.
expect(focusNodeOnPageOne.hasFocus, isTrue);
});
testWidgets('child with local history can be disposed', (WidgetTester tester) async {
// Regression test: https://github.com/flutter/flutter/issues/52478
await tester.pumpWidget(MaterialApp(
home: WidgetWithLocalHistory(),
));
final WidgetWithLocalHistoryState state = tester.state(find.byType(WidgetWithLocalHistory));
state.addLocalHistory();
// Waits for modal route to update its internal state;
await tester.pump();
// Pumps a new widget to dispose WidgetWithLocalHistory. This should cause
// it to remove the local history entry from modal route during
// finalizeTree.
await tester.pumpWidget(const MaterialApp(
home: Text('dummy'),
));
// Waits for modal route to update its internal state;
await tester.pump();
expect(tester.takeException(), null);
});
});
}
......@@ -1390,3 +1412,29 @@ class _TestDialogRouteWithCustomBarrierCurve<T> extends PopupRoute<T> {
);
}
}
class WidgetWithLocalHistory extends StatefulWidget {
@override
WidgetWithLocalHistoryState createState() => WidgetWithLocalHistoryState();
}
class WidgetWithLocalHistoryState extends State<WidgetWithLocalHistory> {
LocalHistoryEntry _localHistory;
void addLocalHistory() {
final ModalRoute<dynamic> route = ModalRoute.of(context);
_localHistory = LocalHistoryEntry();
route.addLocalHistoryEntry(_localHistory);
}
@override
void dispose() {
super.dispose();
_localHistory.remove();
}
@override
Widget build(BuildContext context) {
return const Text('dummy');
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment