@@ -3419,6 +3419,151 @@ void main() {
34193419 expect (find.byType (Placeholder ), findsOneWidget);
34203420 });
34213421 });
3422+
3423+ testWidgets ('SearchAnchor does not dispose external SearchController' , (WidgetTester tester) async {
3424+ final SearchController controller = SearchController ();
3425+ addTearDown (controller.dispose);
3426+ await tester.pumpWidget (
3427+ MaterialApp (
3428+ home: Material (
3429+ child: SearchAnchor (
3430+ searchController: controller,
3431+ builder: (BuildContext context, SearchController controller) {
3432+ return IconButton (
3433+ onPressed: () async {
3434+ controller.openView ();
3435+ },
3436+ icon: const Icon (Icons .search),
3437+ );
3438+ },
3439+ suggestionsBuilder: (BuildContext context, SearchController controller) {
3440+ return < Widget > [];
3441+ },
3442+ ),
3443+ ),
3444+ ));
3445+
3446+ await tester.tap (find.byIcon (Icons .search));
3447+ await tester.pumpAndSettle ();
3448+ await tester.pumpWidget (
3449+ const MaterialApp (
3450+ home: Material (
3451+ child: Text ('disposed' ),
3452+ ),
3453+ ));
3454+ expect (tester.takeException (), isNull);
3455+ ChangeNotifier .debugAssertNotDisposed (controller);
3456+ });
3457+
3458+ testWidgets ('SearchAnchor gracefully closes its search view when disposed' , (WidgetTester tester) async {
3459+ bool disposed = false ;
3460+ late StateSetter setState;
3461+ await tester.pumpWidget (
3462+ MaterialApp (
3463+ home: Material (
3464+ child: StatefulBuilder (
3465+ builder: (BuildContext context, StateSetter stateSetter) {
3466+ setState = stateSetter;
3467+ if (disposed) {
3468+ return const Text ('disposed' );
3469+ }
3470+ return SearchAnchor (
3471+ builder: (BuildContext context, SearchController controller) {
3472+ return IconButton (
3473+ onPressed: () async {
3474+ controller.openView ();
3475+ },
3476+ icon: const Icon (Icons .search),
3477+ );
3478+ },
3479+ suggestionsBuilder: (BuildContext context, SearchController controller) {
3480+ return < Widget > [
3481+ const Text ('suggestion' ),
3482+ ];
3483+ },
3484+ );
3485+ }
3486+ ),
3487+ ),
3488+ ),
3489+ );
3490+
3491+ await tester.tap (find.byIcon (Icons .search));
3492+ await tester.pumpAndSettle ();
3493+ setState (() {
3494+ disposed = true ;
3495+ });
3496+ await tester.pump ();
3497+ // The search menu starts to close but is not disposed yet.
3498+ final EditableText editableText = tester.widget (find.byType (EditableText ));
3499+ final TextEditingController controller = editableText.controller;
3500+ ChangeNotifier .debugAssertNotDisposed (controller);
3501+
3502+ await tester.pumpAndSettle ();
3503+ // The search menu and the internal search controller are now disposed.
3504+ expect (tester.takeException (), isNull);
3505+ expect (find.byType (TextField ), findsNothing);
3506+ FlutterError ? error;
3507+ try {
3508+ ChangeNotifier .debugAssertNotDisposed (controller);
3509+ } on FlutterError catch (e) {
3510+ error = e;
3511+ }
3512+ expect (error, isNotNull);
3513+ expect (error, isFlutterError);
3514+ expect (
3515+ error! .toStringDeep (),
3516+ equalsIgnoringHashCodes (
3517+ 'FlutterError\n '
3518+ ' A SearchController was used after being disposed.\n '
3519+ ' Once you have called dispose() on a SearchController, it can no\n '
3520+ ' longer be used.\n ' ,
3521+ ),
3522+ );
3523+ });
3524+
3525+ // Regression test for https://github.com/flutter/flutter/issues/155180.
3526+ testWidgets ('disposing SearchAnchor during search view exit animation does not crash' ,
3527+ (WidgetTester tester) async {
3528+ final GlobalKey <NavigatorState > key = GlobalKey <NavigatorState >();
3529+ await tester.pumpWidget (
3530+ MaterialApp (
3531+ navigatorKey: key,
3532+ home: Material (
3533+ child: SearchAnchor (
3534+ builder: (BuildContext context, SearchController controller) {
3535+ return IconButton (
3536+ onPressed: () async {
3537+ controller.openView ();
3538+ },
3539+ icon: const Icon (Icons .search),
3540+ );
3541+ },
3542+ suggestionsBuilder: (BuildContext context, SearchController controller) {
3543+ return < Widget > [
3544+ const Text ('suggestion' ),
3545+ ];
3546+ },
3547+ ),
3548+ ),
3549+ ),
3550+ );
3551+
3552+ await tester.tap (find.byIcon (Icons .search));
3553+ await tester.pumpAndSettle ();
3554+ key.currentState! .pop ();
3555+ await tester.pump ();
3556+ await tester.pumpWidget (
3557+ MaterialApp (
3558+ navigatorKey: key,
3559+ home: const Material (
3560+ child: Text ('disposed' ),
3561+ ),
3562+ ),
3563+ );
3564+ await tester.pump ();
3565+ expect (tester.takeException (), isNull);
3566+ });
34223567}
34233568
34243569Future <void > checkSearchBarDefaults (WidgetTester tester, ColorScheme colorScheme, Material material) async {
0 commit comments