Repository: rrousselGit/flutter_hooks (dart)

Issue:
Add OverlayPortalController 
**Is your feature request related to a problem? Please describe.**

There's a [OverlayPortalController](https://api.flutter.dev/flutter/widgets/OverlayPortalController-class.html) class. I'd like to have a hook like `useOverlayPortalController()`

Pull request description:
Fixes #449 

Added useOverlayPortalController. I edited readme or changelog like other pull requests. Please let me know if there should be other changes needed.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced a new hook for managing overlay visibility with automatic lifecycle disposal.
- **Documentation**
	- Updated documentation and changelog to include details about the new hook.
- **Tests**
	- Added tests to verify the functionality and state transitions of the new overlay controller hook.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Patch:
diff --git a/README.md b/README.md
index b2c276b..bf62334 100644
--- a/README.md
+++ b/README.md
@@ -337,29 +337,30 @@ They will take care of creating/updating/disposing an object.
 
 A series of hooks with no particular theme.
 
-| Name                                                                                                                                   | Description                                                                                                                 |
-| -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
-| [useReducer](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useReducer.html)                                         | An alternative to `useState` for more complex states.                                                                       |
-| [usePrevious](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePrevious.html)                                       | Returns the previous argument called to [usePrevious].                                                                      |
-| [useTextEditingController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTextEditingController-constant.html)    | Creates a `TextEditingController`.                                                                                          |
-| [useFocusNode](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFocusNode.html)                                     | Creates a `FocusNode`.                                                                                                      |
-| [useTabController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTabController.html)                             | Creates and disposes a `TabController`.                                                                                     |
-| [useScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useScrollController.html)                       | Creates and disposes a `ScrollController`.                                                                                  |
-| [usePageController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePageController.html)                           | Creates and disposes a `PageController`.                                                                                    |
-| [useFixedExtentScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFixedExtentScrollController.html) | Creates and disposes a `FixedExtentScrollController`.                                                                       |
-| [useAppLifecycleState](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAppLifecycleState.html)                     | Returns the current `AppLifecycleState` and rebuilds the widget on change.                                                  |
-| [useOnAppLifecycleStateChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnAppLifecycleStateChange.html)     | Listens to `AppLifecycleState` changes and triggers a callback on change.                                                   |
-| [useTransformationController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTransformationController.html)       | Creates and disposes a `TransformationController`.                                                                          |
-| [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`.                                                                                  |
-| [useWidgetStatesController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useWidgetStatesController.html)           | Creates and disposes a `WidgetStatesController`.                                                                            |
-| [useExpansionTileController](https://api.flutter.dev/flutter/material/ExpansionTileController-class.html)                              | Creates a `ExpansionTileController`.                                                                                        |
-| [useDebounced](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useDebounced.html)                                     | Returns a debounced version of the provided value, triggering widget updates accordingly after a specified timeout duration |
-| [useDraggableScrollableController](https://api.flutter.dev/flutter/widgets/DraggableScrollableController-class.html)                   | Creates a `DraggableScrollableController`.                                                                                  |
-| [useCarouselController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useCarouselController.html)                   | Creates and disposes a `CarouselController`.                                                                                |
-| [useTreeSliverController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTreeSliverController.html)               | Creates a `TreeSliverController`.                                                                                           |
+| Name                                                                                                                                   | Description                                                                                                                                                              |
+| -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| [useReducer](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useReducer.html)                                         | An alternative to `useState` for more complex states.                                                                                                                    |
+| [usePrevious](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePrevious.html)                                       | Returns the previous argument called to [usePrevious].                                                                                                                   |
+| [useTextEditingController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTextEditingController-constant.html)    | Creates a `TextEditingController`.                                                                                                                                       |
+| [useFocusNode](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFocusNode.html)                                     | Creates a `FocusNode`.                                                                                                                                                   |
+| [useTabController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTabController.html)                             | Creates and disposes a `TabController`.                                                                                                                                  |
+| [useScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useScrollController.html)                       | Creates and disposes a `ScrollController`.                                                                                                                               |
+| [usePageController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePageController.html)                           | Creates and disposes a `PageController`.                                                                                                                                 |
+| [useFixedExtentScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFixedExtentScrollController.html) | Creates and disposes a `FixedExtentScrollController`.                                                                                                                    |
+| [useAppLifecycleState](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAppLifecycleState.html)                     | Returns the current `AppLifecycleState` and rebuilds the widget on change.                                                                                               |
+| [useOnAppLifecycleStateChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnAppLifecycleStateChange.html)     | Listens to `AppLifecycleState` changes and triggers a callback on change.                                                                                                |
+| [useTransformationController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTransformationController.html)       | Creates and disposes a `TransformationController`.                                                                                                                       |
+| [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`.                                                                                                                               |
+| [useWidgetStatesController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useWidgetStatesController.html)           | Creates and disposes a `WidgetStatesController`.                                                                                                                         |
+| [useExpansionTileController](https://api.flutter.dev/flutter/material/ExpansionTileController-class.html)                              | Creates a `ExpansionTileController`.                                                                                                                                     |
+| [useDebounced](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useDebounced.html)                                     | Returns a debounced version of the provided value, triggering widget updates accordingly after a specified timeout duration                                              |
+| [useDraggableScrollableController](https://api.flutter.dev/flutter/widgets/DraggableScrollableController-class.html)                   | Creates a `DraggableScrollableController`.                                                                                                                               |
+| [useCarouselController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useCarouselController.html)                   | Creates and disposes a **`CarouselController`**.                                                                                                                             |
+| [useTreeSliverController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTreeSliverController.html)               | Creates a `TreeSliverController`.                                                                                                                                        |
+| [useOverlayPortalController](https://api.flutter.dev/flutter/widgets/OverlayPortalController-class.html)                               | Creates and manages an `OverlayPortalController` for controlling the visibility of overlay content. The controller will be automatically disposed when no longer needed. |
 
 ## Contributions
 
diff --git a/packages/flutter_hooks/CHANGELOG.md b/packages/flutter_hooks/CHANGELOG.md
index f3fe9e1..84ef28f 100644
--- a/packages/flutter_hooks/CHANGELOG.md
+++ b/packages/flutter_hooks/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 - Add `useCarouselController` (thanks to @riscait)
 - Add `useTreeSliverController` (thanks to @benthillerkus)
+- Added `useOverlayPortalController` (thanks to @offich)
 
 ## 0.21.1-pre.4 - 2024-08-01
 
diff --git a/packages/flutter_hooks/lib/src/misc.dart b/packages/flutter_hooks/lib/src/misc.dart
index 147b765..57510ad 100644
--- a/packages/flutter_hooks/lib/src/misc.dart
+++ b/packages/flutter_hooks/lib/src/misc.dart
@@ -229,3 +229,32 @@ class _IsMountedHookState extends HookState<IsMounted, _IsMountedHook> {
   "Use BuildContext.mounted instead if you're on Flutter 3.7.0 or greater",
 )
 typedef IsMounted = bool Function();
+
+/// Creates a [OverlayPortalController] that will be disposed automatically.
+///
+/// See also:
+/// - [OverlayPortalController]
+OverlayPortalController useOverlayPortalController({
+  List<Object?>? keys,
+}) {
+  return use(_OverlayPortalControllerHook(keys: keys));
+}
+
+class _OverlayPortalControllerHook extends Hook<OverlayPortalController> {
+  const _OverlayPortalControllerHook({List<Object?>? keys}) : super(keys: keys);
+
+  @override
+  HookState<OverlayPortalController, Hook<OverlayPortalController>>
+      createState() => _OverlayPortalControllerHookState();
+}
+
+class _OverlayPortalControllerHookState
+    extends HookState<OverlayPortalController, _OverlayPortalControllerHook> {
+  final controller = OverlayPortalController();
+
+  @override
+  OverlayPortalController build(BuildContext context) => controller;
+
+  @override
+  String get debugLabel => 'useOverlayPortalController';
+}


Test patch:
diff --git a/packages/flutter_hooks/test/use_overlay_portal_controller_test.dart b/packages/flutter_hooks/test/use_overlay_portal_controller_test.dart
new file mode 100644
index 0000000..c922645
--- /dev/null
+++ b/packages/flutter_hooks/test/use_overlay_portal_controller_test.dart
@@ -0,0 +1,91 @@
+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) {
+        useOverlayPortalController();
+        return const SizedBox();
+      }),
+    );
+
+    await tester.pump();
+
+    final element = tester.element(find.byType(HookBuilder));
+
+    expect(
+      element
+          .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage)
+          .toStringDeep(),
+      equalsIgnoringHashCodes(
+        'HookBuilder\n'
+        ' │ useOverlayPortalController: OverlayPortalController DETACHED\n'
+        ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n',
+      ),
+    );
+  });
+
+  group('useOverlayPortalController', () {
+    testWidgets('initial values matches with real constructor', (tester) async {
+      late OverlayPortalController controller;
+      final controller2 = OverlayPortalController();
+
+      await tester.pumpWidget(MaterialApp(
+        home: Scaffold(
+          body: HookBuilder(builder: (context) {
+            controller = useOverlayPortalController();
+            return Column(
+              children: [
+                OverlayPortal(
+                  controller: controller,
+                  overlayChildBuilder: (context) =>
+                      const Text('Overlay Portal'),
+                ),
+                OverlayPortal(
+                  controller: controller2,
+                  overlayChildBuilder: (context) =>
+                      const Text('Overlay Portal 2'),
+                ),
+              ],
+            );
+          }),
+        ),
+      ));
+      expect(controller, isA<OverlayPortalController>());
+      expect(controller.isShowing, controller2.isShowing);
+    });
+
+    testWidgets('check show/hide of overlay portal', (tester) async {
+      late OverlayPortalController controller;
+      await tester.pumpWidget(MaterialApp(
+        home: Scaffold(
+          body: HookBuilder(builder: (context) {
+            controller = useOverlayPortalController();
+            return OverlayPortal(
+              controller: controller,
+              overlayChildBuilder: (context) => const Text('Overlay Content'),
+            );
+          }),
+        ),
+      ));
+
+      expect(controller.isShowing, false);
+      expect(find.text('Overlay Content'), findsNothing);
+
+      controller.show();
+      await tester.pump();
+      expect(controller.isShowing, true);
+      expect(find.text('Overlay Content'), findsOneWidget);
+
+      controller.hide();
+      await tester.pump();
+      expect(controller.isShowing, false);
+      expect(find.text('Overlay Content'), findsNothing);
+    });
+  });
+}