Skip to content

Commit

Permalink
Implement Hook for SearchController (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
snapsl authored Jul 6, 2023
1 parent 07855ac commit 2a6b45c
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ A series of hooks with no particular theme.
| [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`. |

## Contributions

Expand Down
4 changes: 4 additions & 0 deletions packages/flutter_hooks/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased minor

- Added `useSearchController` (thanks to @snapsl)

## 0.18.6

- Added korean translation (thanks to @sejun2)
Expand Down
20 changes: 11 additions & 9 deletions packages/flutter_hooks/lib/src/hooks.dart
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' show Brightness, TabController;
import 'package:flutter/material.dart'
show Brightness, SearchController, TabController;
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';

import 'framework.dart';

part 'animation.dart';
part 'async.dart';
part 'focus_node.dart';
part 'focus_scope_node.dart';
part 'keep_alive.dart';
part 'listenable.dart';
part 'listenable_selector.dart';
part 'misc.dart';
part 'page_controller.dart';
part 'platform_brightness.dart';
part 'primitives.dart';
part 'scroll_controller.dart';
part 'search_controller.dart';
part 'tab_controller.dart';
part 'text_controller.dart';
part 'focus_node.dart';
part 'focus_scope_node.dart';
part 'scroll_controller.dart';
part 'page_controller.dart';
part 'widgets_binding_observer.dart';
part 'transformation_controller.dart';
part 'platform_brightness.dart';
part 'keep_alive.dart';
part 'listenable_selector.dart';
part 'widgets_binding_observer.dart';
31 changes: 31 additions & 0 deletions packages/flutter_hooks/lib/src/search_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
part of 'hooks.dart';

/// Creates a [SearchController] that will be disposed automatically.
///
/// See also:
/// - [SearchController]
SearchController useSearchController({List<Object?>? keys}) {
return use(_SearchControllerHook(keys: keys));
}

class _SearchControllerHook extends Hook<SearchController> {
const _SearchControllerHook({List<Object?>? keys}) : super(keys: keys);

@override
HookState<SearchController, Hook<SearchController>> createState() =>
_SearchControllerHookState();
}

class _SearchControllerHookState
extends HookState<SearchController, _SearchControllerHook> {
final controller = SearchController();

@override
String get debugLabel => 'useSearchController';

@override
SearchController build(BuildContext context) => controller;

@override
void dispose() => controller.dispose();
}
92 changes: 92 additions & 0 deletions packages/flutter_hooks/test/use_search_controller_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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) {
useSearchController();

return const SizedBox();
}),
);

await tester.pump();

final element = tester.element(find.byType(HookBuilder));

expect(
element
.toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage)
.toStringDeep(),
equalsIgnoringHashCodes(
'HookBuilder\n'
' │ useSearchController:\n'
' │ SearchController#00000(TextEditingValue(text: ┤├, selection:\n'
' │ TextSelection.invalid, composing: TextRange(start: -1, end:\n'
' │ -1)))\n'
' └SizedBox(renderObject: RenderConstrainedBox#00000)\n',
),
);
});

group('useSearchController', () {
testWidgets('initial values matches with real constructor', (tester) async {
late SearchController controller;
final controller2 = SearchController();

await tester.pumpWidget(
HookBuilder(builder: (context) {
controller = useSearchController();

return Container();
}),
);

expect(controller, isA<SearchController>());

expect(controller.selection, controller2.selection);
expect(controller.text, controller2.text);
expect(controller.value, controller2.value);
});

testWidgets('check opening/closing view', (tester) async {
late SearchController controller;

await tester.pumpWidget(MaterialApp(
home: HookBuilder(builder: (context) {
controller = useSearchController();

return SearchAnchor.bar(
searchController: controller,
suggestionsBuilder: (context, controller) => [],
);
}),
));

controller.openView();

expect(controller.isOpen, true);

await tester.pumpWidget(MaterialApp(
home: HookBuilder(builder: (context) {
controller = useSearchController();

return SearchAnchor.bar(
searchController: controller,
suggestionsBuilder: (context, controller) => [],
);
}),
));

controller.closeView('selected');

expect(controller.isOpen, false);
expect(controller.text, 'selected');
});
});
}

0 comments on commit 2a6b45c

Please sign in to comment.