Repository: rrousselGit/flutter_hooks
Pull request #447 (2024-10-01 14:01:51)

feat: Add `useCarouselController`
Thanks for the great package!

I have added CarouselController hooks for use with CarouselView, now available in Flutter 3.24 (stable)!

Implementation and testing was based on usePageController and useSearchController.

Please let me know if anything is missing.

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

- **New Features**
	- Enhanced README documentation for better clarity on Flutter Hooks, including examples and rules for using hooks.
	- Introduced `useCarouselController` hook for managing carousel functionality, simplifying state management.

- **Bug Fixes**
	- Improved documentation on hot-reload behavior and custom hook creation.

- **Tests**
	- Added comprehensive tests for the `useCarouselController` hook, validating its functionality and resource management.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

coderabbitai[bot]: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: summarize review in progress by coderabbit.ai -->

> [!NOTE]
> Currently processing new changes in this PR. This may take a few minutes, please wait...
> 
> 	<details>
> <summary>📥 Commits</summary>
> 
> Files that changed from the base of the PR and between 319679b4ff8e4e224b3d9c29fd138623f750a24d and 2b1d88e971ce58d34edfe2132866fde49091349f.
> 
> </details>
> 
> <details>
> <summary>📒 Files selected for processing (4)</summary>
> 
> * README.md (1 hunks)
> * packages/flutter_hooks/lib/src/carousel_controller.dart (1 hunks)
> * packages/flutter_hooks/lib/src/hooks.dart (2 hunks)
> * packages/flutter_hooks/test/carousel_controller_test.dart (1 hunks)
> 
> </details>
> 
> 	```ascii
>  ________________________________________________________
> < Code review is a dish best served cold. Like a carrot. >
>  --------------------------------------------------------
>   \
>    \   \
>         \ /\
>         ( )
>       .( o ).
> ```

<!-- end of auto-generated comment: summarize review in progress by coderabbit.ai --><!-- usage_tips_start -->

> [!TIP]
> <details>
> <summary>You can validate your CodeRabbit configuration file in your editor.</summary>
> 
> If your editor has YAML language server, you can enable auto-completion and validation by adding `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json` at the top of your CodeRabbit configuration file.
> 
> </details>

<!-- usage_tips_end --><!-- tips_start -->

---

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://coderabbit.ai/docs) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
rrousselGit: Do you mind fixing the CI?
riscait: > Do you mind fixing the CI?

Sorry, I missed it! Please fix it.
rrousselGit: I was asking you to do it :)
riscait: Oh, my translation was wrong:(
I ran the dart format command on the master channel, so hopefully that will improve things!
riscait: @rrousselGit 
Thank you for your time.
I’ve updated the formatting as requested—whenever convenient, could you please trigger the CI again?


Patch:
diff --git a/README.md b/README.md
index 99aff23..246d050 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 [English](https://github.com/rrousselGit/flutter_hooks/blob/master/README.md) | [Português](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/pt_br/README.md) | [한국어](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/ko_kr/README.md) | [简体中文](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/zh_cn/README.md)
 
 [![Build](https://github.com/rrousselGit/flutter_hooks/workflows/Build/badge.svg)](https://github.com/rrousselGit/flutter_hooks/actions?query=workflow%3ABuild) [![codecov](https://codecov.io/gh/rrousselGit/flutter_hooks/branch/master/graph/badge.svg)](https://codecov.io/gh/rrousselGit/flutter_hooks) [![pub package](https://img.shields.io/pub/v/flutter_hooks.svg)](https://pub.dev/packages/flutter_hooks) [![pub package](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter)
-<a href="https://discord.gg/Bbumvej"><img src="https://img.shields.io/discord/765557403865186374.svg?logo=discord&color=blue" alt="Discord"></a>
+<a href="https://discord.gg/6G6ZWkk3fQ"><img src="https://img.shields.io/discord/765557403865186374.svg?logo=discord&color=blue" alt="Discord"></a>
 
 <img src="https://raw.githubusercontent.com/rrousselGit/flutter_hooks/master/packages/flutter_hooks/flutter-hook.svg?sanitize=true" width="200">
 
@@ -358,6 +358,7 @@ A series of hooks with no particular theme.
 | [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`.                                                                                  |
 
 ## Contributions
 
diff --git a/packages/flutter_hooks/lib/src/carousel_controller.dart b/packages/flutter_hooks/lib/src/carousel_controller.dart
new file mode 100644
index 0000000..b1ec352
--- /dev/null
+++ b/packages/flutter_hooks/lib/src/carousel_controller.dart
@@ -0,0 +1,46 @@
+part of 'hooks.dart';
+
+/// Creates a [CarouselController] that will be disposed automatically.
+///
+/// See also:
+/// - [CarouselController]
+CarouselController useCarouselController({
+  int initialItem = 0,
+  List<Object?>? keys,
+}) {
+  return use(
+    _CarouselControllerHook(
+      initialItem: initialItem,
+      keys: keys,
+    ),
+  );
+}
+
+class _CarouselControllerHook extends Hook<CarouselController> {
+  const _CarouselControllerHook({
+    required this.initialItem,
+    super.keys,
+  });
+
+  final int initialItem;
+
+  @override
+  HookState<CarouselController, Hook<CarouselController>> createState() =>
+      _CarouselControllerHookState();
+}
+
+class _CarouselControllerHookState
+    extends HookState<CarouselController, _CarouselControllerHook> {
+  late final controller = CarouselController(
+    initialItem: hook.initialItem,
+  );
+
+  @override
+  CarouselController build(BuildContext context) => controller;
+
+  @override
+  void dispose() => controller.dispose();
+
+  @override
+  String get debugLabel => 'useCarouselController';
+}
diff --git a/packages/flutter_hooks/lib/src/hooks.dart b/packages/flutter_hooks/lib/src/hooks.dart
index 023868d..a7ee1e9 100644
--- a/packages/flutter_hooks/lib/src/hooks.dart
+++ b/packages/flutter_hooks/lib/src/hooks.dart
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart'
     show
         Brightness,
+        CarouselController,
         DraggableScrollableController,
         ExpansionTileController,
         WidgetStatesController,
@@ -17,6 +18,7 @@ import 'framework.dart';
 
 part 'animation.dart';
 part 'async.dart';
+part 'carousel_controller.dart';
 part 'draggable_scrollable_controller.dart';
 part 'expansion_tile_controller.dart';
 part 'fixed_extent_scroll_controller.dart';
diff --git a/packages/flutter_hooks/resources/translations/ko_kr/README.md b/packages/flutter_hooks/resources/translations/ko_kr/README.md
index 53428f3..73d9efe 100644
--- a/packages/flutter_hooks/resources/translations/ko_kr/README.md
+++ b/packages/flutter_hooks/resources/translations/ko_kr/README.md
@@ -1,7 +1,7 @@
 [English](https://github.com/rrousselGit/flutter_hooks/blob/master/README.md) | [Português](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/pt_br/README.md) | [한국어](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/ko_kr/README.md) | [简体中文](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/zh_cn/README.md)
 
 [![Build](https://github.com/rrousselGit/flutter_hooks/workflows/Build/badge.svg)](https://github.com/rrousselGit/flutter_hooks/actions?query=workflow%3ABuild) [![codecov](https://codecov.io/gh/rrousselGit/flutter_hooks/branch/master/graph/badge.svg)](https://codecov.io/gh/rrousselGit/flutter_hooks) [![pub package](https://img.shields.io/pub/v/flutter_hooks.svg)](https://pub.dartlang.org/packages/flutter_hooks) [![pub package](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter)
-<a href="https://discord.gg/Bbumvej"><img src="https://img.shields.io/discord/765557403865186374.svg?logo=discord&color=blue" alt="Discord"></a>
+<a href="https://discord.gg/6G6ZWkk3fQ"><img src="https://img.shields.io/discord/765557403865186374.svg?logo=discord&color=blue" alt="Discord"></a>
 
 <img src="https://raw.githubusercontent.com/rrousselGit/flutter_hooks/master/packages/flutter_hooks/flutter-hook.svg?sanitize=true" width="200">
 
diff --git a/packages/flutter_hooks/resources/translations/zh_cn/README.md b/packages/flutter_hooks/resources/translations/zh_cn/README.md
index e33f13d..1aa1255 100644
--- a/packages/flutter_hooks/resources/translations/zh_cn/README.md
+++ b/packages/flutter_hooks/resources/translations/zh_cn/README.md
@@ -2,7 +2,7 @@
 
 [![Build](https://github.com/rrousselGit/flutter_hooks/workflows/Build/badge.svg)](https://github.com/rrousselGit/flutter_hooks/actions?query=workflow%3ABuild) [![codecov](https://codecov.io/gh/rrousselGit/flutter_hooks/branch/master/graph/badge.svg)](https://codecov.io/gh/rrousselGit/flutter_hooks) [![pub package](https://img.shields.io/pub/v/flutter_hooks.svg)](https://pub.dev/packages/flutter_hooks) [![pub package](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter)
 
-<a href="https://discord.gg/Bbumvej"><img src="https://img.shields.io/discord/765557403865186374.svg?logo=discord&color=blue" alt="Discord"></a>
+<a href="https://discord.gg/6G6ZWkk3fQ"><img src="https://img.shields.io/discord/765557403865186374.svg?logo=discord&color=blue" alt="Discord"></a>
 
 <p align="center">
   <img src="https://raw.githubusercontent.com/rrousselGit/flutter_hooks/master/packages/flutter_hooks/flutter-hook.svg?sanitize=true" width="200">


Test patch:
diff --git a/packages/flutter_hooks/test/carousel_controller_test.dart b/packages/flutter_hooks/test/carousel_controller_test.dart
new file mode 100644
index 0000000..b75c90d
--- /dev/null
+++ b/packages/flutter_hooks/test/carousel_controller_test.dart
@@ -0,0 +1,117 @@
+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 'package:flutter_test/flutter_test.dart';
+
+import 'mock.dart';
+
+void main() {
+  testWidgets('debugFillProperties', (tester) async {
+    await tester.pumpWidget(
+      HookBuilder(builder: (context) {
+        useCarouselController();
+        return const SizedBox();
+      }),
+    );
+
+    final element = tester.element(find.byType(HookBuilder));
+
+    expect(
+      element
+          .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage)
+          .toStringDeep(),
+      equalsIgnoringHashCodes(
+        'HookBuilder\n'
+        ' │ useCarouselController: CarouselController#00000(no clients)\n'
+        ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n',
+      ),
+    );
+  });
+
+  group('useCarouselController', () {
+    testWidgets('initial values matches with real constructor', (tester) async {
+      late CarouselController controller;
+      late CarouselController controller2;
+
+      await tester.pumpWidget(
+        HookBuilder(builder: (context) {
+          controller2 = CarouselController();
+          controller = useCarouselController();
+          return Container();
+        }),
+      );
+
+      expect(controller.initialItem, controller2.initialItem);
+      expect(controller.initialScrollOffset, controller2.initialScrollOffset);
+      expect(controller.keepScrollOffset, controller2.keepScrollOffset);
+      expect(controller.onAttach, controller2.onAttach);
+      expect(controller.onDetach, controller2.onDetach);
+    });
+
+    testWidgets("returns a CarouselController that doesn't change",
+        (tester) async {
+      late CarouselController controller;
+      late CarouselController controller2;
+
+      await tester.pumpWidget(
+        HookBuilder(builder: (context) {
+          controller = useCarouselController();
+          return Container();
+        }),
+      );
+
+      expect(controller, isA<CarouselController>());
+
+      await tester.pumpWidget(
+        HookBuilder(builder: (context) {
+          controller2 = useCarouselController();
+          return Container();
+        }),
+      );
+
+      expect(identical(controller, controller2), isTrue);
+    });
+
+    testWidgets('passes hook parameters to the CarouselController',
+        (tester) async {
+      late CarouselController controller;
+
+      await tester.pumpWidget(
+        HookBuilder(
+          builder: (context) {
+            controller = useCarouselController(
+              initialItem: 42,
+            );
+
+            return Container();
+          },
+        ),
+      );
+
+      expect(controller.initialItem, 42);
+    });
+
+    testWidgets('disposes the CarouselController on unmount', (tester) async {
+      late CarouselController controller;
+
+      await tester.pumpWidget(
+        HookBuilder(
+          builder: (context) {
+            controller = useCarouselController();
+            return Container();
+          },
+        ),
+      );
+
+      // pump another widget so that the old one gets disposed
+      await tester.pumpWidget(Container());
+
+      expect(
+        () => controller.addListener(() {}),
+        throwsA(isFlutterError.having(
+            (e) => e.message, 'message', contains('disposed'))),
+      );
+    });
+  });
+}