Repository: rrousselGit/flutter_hooks
Pull request #460 (2025-02-19 13:39:28)

Implement useTreeSliverController
Adds a Hook for the [TreeSliverController](https://api.flutter.dev/flutter/widgets/TreeSliver-class.html)

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

## Summary by CodeRabbit

- **New Features**
  - Introduced a new hook for managing tree view controllers, offering automatic lifecycle handling and streamlined state management.

- **Refactor**
  - Improved internal organization of module declarations and import statements for enhanced maintainability.

- **Tests**
  - Added test coverage to validate the new tree view controller’s behavior, including proper node expansion, collapse functionality, and diagnostic output.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Patch:
diff --git a/packages/flutter_hooks/lib/src/hooks.dart b/packages/flutter_hooks/lib/src/hooks.dart
index a7ee1e9..8cce956 100644
--- a/packages/flutter_hooks/lib/src/hooks.dart
+++ b/packages/flutter_hooks/lib/src/hooks.dart
@@ -10,6 +10,7 @@ import 'package:flutter/material.dart'
         WidgetStatesController,
         SearchController,
         TabController,
+        TreeSliverController,
         WidgetState;
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/widgets.dart';
@@ -19,6 +20,7 @@ import 'framework.dart';
 part 'animation.dart';
 part 'async.dart';
 part 'carousel_controller.dart';
+part 'debounced.dart';
 part 'draggable_scrollable_controller.dart';
 part 'expansion_tile_controller.dart';
 part 'fixed_extent_scroll_controller.dart';
@@ -36,6 +38,6 @@ part 'search_controller.dart';
 part 'tab_controller.dart';
 part 'text_controller.dart';
 part 'transformation_controller.dart';
-part 'widgets_binding_observer.dart';
+part 'tree_sliver_controller.dart';
 part 'widget_states_controller.dart';
-part 'debounced.dart';
+part 'widgets_binding_observer.dart';
diff --git a/packages/flutter_hooks/lib/src/tree_sliver_controller.dart b/packages/flutter_hooks/lib/src/tree_sliver_controller.dart
new file mode 100644
index 0000000..0accf1a
--- /dev/null
+++ b/packages/flutter_hooks/lib/src/tree_sliver_controller.dart
@@ -0,0 +1,28 @@
+part of 'hooks.dart';
+
+/// Creates a [TreeSliverController] that will be disposed automatically.
+///
+/// See also:
+/// - [TreeSliverController]
+TreeSliverController useTreeSliverController() {
+  return use(const _TreeSliverControllerHook());
+}
+
+class _TreeSliverControllerHook extends Hook<TreeSliverController> {
+  const _TreeSliverControllerHook();
+
+  @override
+  HookState<TreeSliverController, Hook<TreeSliverController>> createState() =>
+      _TreeSliverControllerHookState();
+}
+
+class _TreeSliverControllerHookState
+    extends HookState<TreeSliverController, _TreeSliverControllerHook> {
+  final controller = TreeSliverController();
+
+  @override
+  String get debugLabel => 'useTreeSliverController';
+
+  @override
+  TreeSliverController build(BuildContext context) => controller;
+}


Test patch:
diff --git a/packages/flutter_hooks/test/use_tree_sliver_controller_test.dart b/packages/flutter_hooks/test/use_tree_sliver_controller_test.dart
new file mode 100644
index 0000000..48646b4
--- /dev/null
+++ b/packages/flutter_hooks/test/use_tree_sliver_controller_test.dart
@@ -0,0 +1,68 @@
+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) {
+        useTreeSliverController();
+        return const SizedBox();
+      }),
+    );
+
+    await tester.pump();
+
+    final element = tester.element(find.byType(HookBuilder));
+
+    expect(
+      element
+          .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage)
+          .toStringDeep(),
+      equalsIgnoringHashCodes(
+        'HookBuilder\n'
+        " │ useTreeSliverController: Instance of 'TreeSliverController'\n"
+        ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n',
+      ),
+    );
+  });
+
+  group('useTreeSliverController', () {
+    testWidgets('check expansion/collapse of node', (tester) async {
+      late TreeSliverController controller;
+      final tree = <TreeSliverNode<int>>[
+        TreeSliverNode(0, children: [TreeSliverNode(1), TreeSliverNode(2)]),
+        TreeSliverNode(
+            expanded: true, 3, children: [TreeSliverNode(4), TreeSliverNode(5)])
+      ];
+      await tester.pumpWidget(MaterialApp(
+        home: Scaffold(
+          body: HookBuilder(builder: (context) {
+            controller = useTreeSliverController();
+            return CustomScrollView(slivers: [
+              TreeSliver(
+                controller: controller,
+                tree: tree,
+              ),
+            ]);
+          }),
+        ),
+      ));
+
+      expect(controller.isExpanded(tree[0]), false);
+      controller.expandNode(tree[0]);
+      expect(controller.isExpanded(tree[0]), true);
+      controller.collapseNode(tree[0]);
+      expect(controller.isExpanded(tree[0]), false);
+
+      expect(controller.isExpanded(tree[1]), true);
+      controller.collapseNode(tree[1]);
+      expect(controller.isExpanded(tree[1]), false);
+      controller.expandNode(tree[1]);
+      expect(controller.isExpanded(tree[1]), true);
+    });
+  });
+}