Repository: rrousselGit/flutter_hooks (dart)

Issue:
Missing hook for `ExpansionTileController`
There's a [`ExpansionTileController`](https://api.flutter.dev/flutter/material/ExpansionTileController-class.html) class. I'd like to have a hook like `useExpansionTileController()` :)

Pull request description:
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [X] Feature
- [ ] Fix
- [ ] Optimization

## Description
This feature adds useExpansionTileController hook with which we can expand / collapse an ExpansionTile
- Added Testcases.
- Updated Readme. 
## Related Tickets & Documents

- Closes #375

Patch:
diff --git a/README.md b/README.md
index 94fc208f..558dfe67 100644
--- a/README.md
+++ b/README.md
@@ -338,22 +338,23 @@ 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`.                                   |
-| [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`.                                 |
+| 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`.                                    |
+| [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`.                                  |
+| [useExpansionTileController](https://api.flutter.dev/flutter/material/ExpansionTileController-class.html)                                     | Creates a `ExpansionTileController`.                                        |
 
 ## Contributions
 
diff --git a/packages/flutter_hooks/lib/src/expansion_tile_controller.dart b/packages/flutter_hooks/lib/src/expansion_tile_controller.dart
new file mode 100644
index 00000000..f698f54b
--- /dev/null
+++ b/packages/flutter_hooks/lib/src/expansion_tile_controller.dart
@@ -0,0 +1,28 @@
+part of 'hooks.dart';
+
+/// Creates a [ExpansionTileController] that will be disposed automatically.
+///
+/// See also:
+/// - [ExpansionTileController]
+ExpansionTileController useExpansionTileController({List<Object?>? keys}) {
+  return use(_ExpansionTileControllerHook(keys: keys));
+}
+
+class _ExpansionTileControllerHook extends Hook<ExpansionTileController> {
+  const _ExpansionTileControllerHook({List<Object?>? keys}) : super(keys: keys);
+
+  @override
+  HookState<ExpansionTileController, Hook<ExpansionTileController>>
+      createState() => _ExpansionTileControllerHookState();
+}
+
+class _ExpansionTileControllerHookState
+    extends HookState<ExpansionTileController, _ExpansionTileControllerHook> {
+  final controller = ExpansionTileController();
+
+  @override
+  String get debugLabel => 'useExpansionTileController';
+
+  @override
+  ExpansionTileController build(BuildContext context) => controller;
+}
diff --git a/packages/flutter_hooks/lib/src/hooks.dart b/packages/flutter_hooks/lib/src/hooks.dart
index 036289fd..d23ebabb 100644
--- a/packages/flutter_hooks/lib/src/hooks.dart
+++ b/packages/flutter_hooks/lib/src/hooks.dart
@@ -2,7 +2,7 @@ import 'dart:async';
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart'
-    show Brightness, SearchController, TabController;
+    show Brightness, ExpansionTileController, SearchController, TabController;
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/widgets.dart';
 
@@ -10,6 +10,7 @@ import 'framework.dart';
 
 part 'animation.dart';
 part 'async.dart';
+part 'expansion_tile_controller.dart';
 part 'focus_node.dart';
 part 'focus_scope_node.dart';
 part 'keep_alive.dart';


Test patch:
diff --git a/packages/flutter_hooks/test/use_expansion_tile_controller_test.dart b/packages/flutter_hooks/test/use_expansion_tile_controller_test.dart
new file mode 100644
index 00000000..84dd57d1
--- /dev/null
+++ b/packages/flutter_hooks/test/use_expansion_tile_controller_test.dart
@@ -0,0 +1,82 @@
+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) {
+        useExpansionTileController();
+        return const SizedBox();
+      }),
+    );
+
+    await tester.pump();
+
+    final element = tester.element(find.byType(HookBuilder));
+
+    expect(
+      element
+          .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage)
+          .toStringDeep(),
+      equalsIgnoringHashCodes(
+        'HookBuilder\n'
+        " │ useExpansionTileController: Instance of 'ExpansionTileController'\n"
+        ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n',
+      ),
+    );
+  });
+
+  group('useExpansionTileController', () {
+    testWidgets('initial values matches with real constructor', (tester) async {
+      late ExpansionTileController controller;
+      final controller2 = ExpansionTileController();
+
+      await tester.pumpWidget(MaterialApp(
+        home: Scaffold(
+          body: HookBuilder(builder: (context) {
+            controller = useExpansionTileController();
+            return Column(
+              children: [
+                ExpansionTile(
+                  controller: controller,
+                  title: const Text('Expansion Tile'),
+                ),
+                ExpansionTile(
+                  controller: controller2,
+                  title: const Text('Expansion Tile 2'),
+                ),
+              ],
+            );
+          }),
+        ),
+      ));
+      expect(controller, isA<ExpansionTileController>());
+      expect(controller.isExpanded, controller2.isExpanded);
+    });
+
+    testWidgets('check expansion/collapse of tile', (tester) async {
+      late ExpansionTileController controller;
+      await tester.pumpWidget(MaterialApp(
+        home: Scaffold(
+          body: HookBuilder(builder: (context) {
+            controller = useExpansionTileController();
+            return ExpansionTile(
+              controller: controller,
+              title: const Text('Expansion Tile'),
+            );
+          }),
+        ),
+      ));
+
+      expect(controller.isExpanded, false);
+      controller.expand();
+      expect(controller.isExpanded, true);
+      controller.collapse();
+      expect(controller.isExpanded, false);
+    });
+  });
+}