Repository: rrousselGit/flutter_hooks

Issue:
SearchController Hook
Hi, 
It would be great to have a new hook for the [SearchController](https://api.flutter.dev/flutter/material/SearchController-class.html) which is used for the [SearchAnchor](https://api.flutter.dev/flutter/material/SearchAnchor-class.html) widget.


Discussion: Fair enough. Although low priority for me. Feel free to open a PR :) 

Fix patch:
diff --git a/README.md b/README.md
index c28a1583..6aaefa04 100644
--- a/README.md
+++ b/README.md
@@ -352,6 +352,7 @@ A series of hooks with no particular theme.
 | [useIsMounted](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useIsMounted.html)                                   | An equivalent to `State.mounted` for hooks.                                |
 | [useAutomaticKeepAlive](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAutomaticKeepAlive.html)                 | An equivalent to the `AutomaticKeepAlive` widget for hooks.                |
 | [useOnPlatformBrightnessChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnPlatformBrightnessChange.html) | Listens to platform `Brightness` changes and triggers a callback on change.|
+| [useSearchController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useSearchController.html)                     | Creates and disposes a `SearchController`.                                 |
 
 ## Contributions
 
diff --git a/packages/flutter_hooks/CHANGELOG.md b/packages/flutter_hooks/CHANGELOG.md
index 4cd7f74c..23f73e76 100644
--- a/packages/flutter_hooks/CHANGELOG.md
+++ b/packages/flutter_hooks/CHANGELOG.md
@@ -1,3 +1,7 @@
+## Unreleased minor
+
+- Added `useSearchController` (thanks to @snapsl)
+
 ## 0.18.6
 
 - Added korean translation (thanks to @sejun2)
diff --git a/packages/flutter_hooks/lib/src/hooks.dart b/packages/flutter_hooks/lib/src/hooks.dart
index 8b8cb7c4..036289fd 100644
--- a/packages/flutter_hooks/lib/src/hooks.dart
+++ b/packages/flutter_hooks/lib/src/hooks.dart
@@ -1,7 +1,8 @@
 import 'dart:async';
 
 import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart' show Brightness, TabController;
+import 'package:flutter/material.dart'
+    show Brightness, SearchController, TabController;
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/widgets.dart';
 
@@ -9,17 +10,18 @@ import 'framework.dart';
 
 part 'animation.dart';
 part 'async.dart';
+part 'focus_node.dart';
+part 'focus_scope_node.dart';
+part 'keep_alive.dart';
 part 'listenable.dart';
+part 'listenable_selector.dart';
 part 'misc.dart';
+part 'page_controller.dart';
+part 'platform_brightness.dart';
 part 'primitives.dart';
+part 'scroll_controller.dart';
+part 'search_controller.dart';
 part 'tab_controller.dart';
 part 'text_controller.dart';
-part 'focus_node.dart';
-part 'focus_scope_node.dart';
-part 'scroll_controller.dart';
-part 'page_controller.dart';
-part 'widgets_binding_observer.dart';
 part 'transformation_controller.dart';
-part 'platform_brightness.dart';
-part 'keep_alive.dart';
-part 'listenable_selector.dart';
+part 'widgets_binding_observer.dart';
diff --git a/packages/flutter_hooks/lib/src/search_controller.dart b/packages/flutter_hooks/lib/src/search_controller.dart
new file mode 100644
index 00000000..454ea6b0
--- /dev/null
+++ b/packages/flutter_hooks/lib/src/search_controller.dart
@@ -0,0 +1,31 @@
+part of 'hooks.dart';
+
+/// Creates a [SearchController] that will be disposed automatically.
+///
+/// See also:
+/// - [SearchController]
+SearchController useSearchController({List<Object?>? keys}) {
+  return use(_SearchControllerHook(keys: keys));
+}
+
+class _SearchControllerHook extends Hook<SearchController> {
+  const _SearchControllerHook({List<Object?>? keys}) : super(keys: keys);
+
+  @override
+  HookState<SearchController, Hook<SearchController>> createState() =>
+      _SearchControllerHookState();
+}
+
+class _SearchControllerHookState
+    extends HookState<SearchController, _SearchControllerHook> {
+  final controller = SearchController();
+
+  @override
+  String get debugLabel => 'useSearchController';
+
+  @override
+  SearchController build(BuildContext context) => controller;
+
+  @override
+  void dispose() => controller.dispose();
+}


Test patch:
diff --git a/packages/flutter_hooks/test/use_search_controller_test.dart b/packages/flutter_hooks/test/use_search_controller_test.dart
new file mode 100644
index 00000000..10061ae1
--- /dev/null
+++ b/packages/flutter_hooks/test/use_search_controller_test.dart
@@ -0,0 +1,92 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/src/framework.dart';
+import 'package:flutter_hooks/src/hooks.dart';
+
+import 'mock.dart';
+
+void main() {
+  testWidgets('debugFillProperties', (tester) async {
+    await tester.pumpWidget(
+      HookBuilder(builder: (context) {
+        useSearchController();
+
+        return const SizedBox();
+      }),
+    );
+
+    await tester.pump();
+
+    final element = tester.element(find.byType(HookBuilder));
+
+    expect(
+      element
+          .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage)
+          .toStringDeep(),
+      equalsIgnoringHashCodes(
+        'HookBuilder\n'
+        ' │ useSearchController:\n'
+        ' │   SearchController#00000(TextEditingValue(text: ┤├, selection:\n'
+        ' │   TextSelection.invalid, composing: TextRange(start: -1, end:\n'
+        ' │   -1)))\n'
+        ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n',
+      ),
+    );
+  });
+
+  group('useSearchController', () {
+    testWidgets('initial values matches with real constructor', (tester) async {
+      late SearchController controller;
+      final controller2 = SearchController();
+
+      await tester.pumpWidget(
+        HookBuilder(builder: (context) {
+          controller = useSearchController();
+
+          return Container();
+        }),
+      );
+
+      expect(controller, isA<SearchController>());
+
+      expect(controller.selection, controller2.selection);
+      expect(controller.text, controller2.text);
+      expect(controller.value, controller2.value);
+    });
+
+    testWidgets('check opening/closing view', (tester) async {
+      late SearchController controller;
+
+      await tester.pumpWidget(MaterialApp(
+        home: HookBuilder(builder: (context) {
+          controller = useSearchController();
+
+          return SearchAnchor.bar(
+            searchController: controller,
+            suggestionsBuilder: (context, controller) => [],
+          );
+        }),
+      ));
+
+      controller.openView();
+
+      expect(controller.isOpen, true);
+
+      await tester.pumpWidget(MaterialApp(
+        home: HookBuilder(builder: (context) {
+          controller = useSearchController();
+
+          return SearchAnchor.bar(
+            searchController: controller,
+            suggestionsBuilder: (context, controller) => [],
+          );
+        }),
+      ));
+
+      controller.closeView('selected');
+
+      expect(controller.isOpen, false);
+      expect(controller.text, 'selected');
+    });
+  });
+}