@@ -3419,6 +3419,151 @@ void main() {
3419
3419
expect (find.byType (Placeholder ), findsOneWidget);
3420
3420
});
3421
3421
});
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
+ });
3422
3567
}
3423
3568
3424
3569
Future <void > checkSearchBarDefaults (WidgetTester tester, ColorScheme colorScheme, Material material) async {
0 commit comments