Repository: rrousselGit/flutter_hooks (dart)

Issue:
Add useCupertinoTabController
We already have `useTabController`. Why not have the `useCupertinoTabController` for everyone that uses the `flutter/cupertino.dart` library?

- [CupertinoTabController](https://api.flutter.dev/flutter/cupertino/CupertinoTabController-class.html)

Pull request description:
Hi Remi 👋

Resolves #222

Introduce a new hook for managing the lifecycle of CupertinoTabController, ensuring it disposes automatically. Include tests to verify its functionality and correct behavior.

no towel required. 🪐

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

- **New Features**
  - Introduced a new hook for managing a Cupertino tab controller with support for setting the initial tab index.

- **Tests**
  - Added comprehensive widget tests to verify the behavior and properties of the new Cupertino tab controller hook.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Patch:
diff --git a/README.md b/README.md
index 4f014f8..a50587e 100644
--- a/README.md
+++ b/README.md
@@ -360,8 +360,9 @@ A series of hooks with no particular theme.
 | [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. |
-| [useSnapshotController](https://api.flutter.dev/flutter/widgets/SnapshotController-class.html)                                         | Creates and manages a `SnapshotController`                                                                                                                               |
+| [useOverlayPortalController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOverlayPortalController.html)         | Creates and manages an `OverlayPortalController` for controlling the visibility of overlay content. The controller will be automatically disposed when no longer needed. |
+| [useSnapshotController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useSnapshotController.html)                   | Creates and manages a `SnapshotController`                                                                                                                               |
+| [useCupertinoController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useCupertinoController.html)                 | Creates and manages a `CupertinoController`                                                                                                                              |
 
 ## Contributions
 
diff --git a/packages/flutter_hooks/lib/src/cupertino_tab_controller.dart b/packages/flutter_hooks/lib/src/cupertino_tab_controller.dart
new file mode 100644
index 0000000..53e7114
--- /dev/null
+++ b/packages/flutter_hooks/lib/src/cupertino_tab_controller.dart
@@ -0,0 +1,46 @@
+part of 'hooks.dart';
+
+/// Creates a [CupertinoTabController] that will be disposed automatically.
+///
+/// See also:
+/// - [CupertinoTabController]
+CupertinoTabController useCupertinoTabController({
+  int initialIndex = 0,
+  List<Object?>? keys,
+}) {
+  return use(
+    _CupertinoTabControllerHook(
+      initialIndex: initialIndex,
+      keys: keys,
+    ),
+  );
+}
+
+class _CupertinoTabControllerHook extends Hook<CupertinoTabController> {
+  const _CupertinoTabControllerHook({
+    required this.initialIndex,
+    super.keys,
+  });
+
+  final int initialIndex;
+
+  @override
+  HookState<CupertinoTabController, Hook<CupertinoTabController>>
+      createState() => _CupertinoTabControllerHookState();
+}
+
+class _CupertinoTabControllerHookState
+    extends HookState<CupertinoTabController, _CupertinoTabControllerHook> {
+  late final controller = CupertinoTabController(
+    initialIndex: hook.initialIndex,
+  );
+
+  @override
+  CupertinoTabController build(BuildContext context) => controller;
+
+  @override
+  void dispose() => controller.dispose();
+
+  @override
+  String get debugLabel => 'useCupertinoTabController';
+}
diff --git a/packages/flutter_hooks/lib/src/hooks.dart b/packages/flutter_hooks/lib/src/hooks.dart
index 1816175..f9b04a9 100644
--- a/packages/flutter_hooks/lib/src/hooks.dart
+++ b/packages/flutter_hooks/lib/src/hooks.dart
@@ -1,5 +1,6 @@
 import 'dart:async';
 
+import 'package:flutter/cupertino.dart' show CupertinoTabController;
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart'
     show
@@ -21,6 +22,7 @@ import 'framework.dart';
 part 'animation.dart';
 part 'async.dart';
 part 'carousel_controller.dart';
+part 'cupertino_tab_controller.dart';
 part 'debounced.dart';
 part 'draggable_scrollable_controller.dart';
 part 'expansion_tile_controller.dart';


Test patch:
diff --git a/packages/flutter_hooks/test/use_cupertino_tab_controller_test.dart b/packages/flutter_hooks/test/use_cupertino_tab_controller_test.dart
new file mode 100644
index 0000000..a57748a
--- /dev/null
+++ b/packages/flutter_hooks/test/use_cupertino_tab_controller_test.dart
@@ -0,0 +1,89 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.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) {
+        useCupertinoTabController();
+        return const SizedBox();
+      }),
+    );
+
+    await tester.pump();
+
+    final element = tester.element(find.byType(HookBuilder));
+
+    expect(
+      element
+          .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage)
+          .toStringDeep(),
+      equalsIgnoringHashCodes(
+        'HookBuilder\n'
+        " │ useCupertinoTabController: Instance of 'CupertinoTabController'\n"
+        ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n',
+      ),
+    );
+  });
+
+  group('useCupertinoTabController', () {
+    testWidgets('initial values matches with real constructor', (tester) async {
+      late CupertinoTabController controller;
+      late CupertinoTabController controller2;
+
+      await tester.pumpWidget(
+        HookBuilder(builder: (context) {
+          controller2 = CupertinoTabController();
+          controller = useCupertinoTabController();
+          return Container();
+        }),
+      );
+
+      expect(controller.index, controller2.index);
+    });
+    testWidgets("returns a CupertinoTabController that doesn't change",
+        (tester) async {
+      late CupertinoTabController controller;
+      late CupertinoTabController controller2;
+
+      await tester.pumpWidget(
+        HookBuilder(builder: (context) {
+          controller = useCupertinoTabController(initialIndex: 1);
+          return Container();
+        }),
+      );
+
+      expect(controller, isA<CupertinoTabController>());
+
+      await tester.pumpWidget(
+        HookBuilder(builder: (context) {
+          controller2 = useCupertinoTabController(initialIndex: 1);
+          return Container();
+        }),
+      );
+
+      expect(identical(controller, controller2), isTrue);
+    });
+
+    testWidgets('passes hook parameters to the CupertinoTabController',
+        (tester) async {
+      late CupertinoTabController controller;
+
+      await tester.pumpWidget(
+        HookBuilder(
+          builder: (context) {
+            controller = useCupertinoTabController(initialIndex: 2);
+
+            return Container();
+          },
+        ),
+      );
+
+      expect(controller.index, 2);
+    });
+  });
+}