Repository: rrousselGit/flutter_hooks
Pull request #377 (2023-08-15 12:38:01)

Feature: ability to use nullable listener in useListenableSelector
## Description

This Pull Request adds the ability to pass `null` as a parameter to the `useListenableSelector` hook. Previously, you could only pass a `Listener` instance, but now you can also pass `Listener?` to the function.

This change is necessary when the listener is initialized later, for example, on the second render. By allowing `null` as a parameter, developers can delay the initialization of the listener until it's ready.

## Example Usage

```dart
// Before
useListenableSelector<SomeListener>(myListener, () => myListener.data);

// After
useListenableSelector<SomeListener?>(null, () => myListener?.data);


rrousselGit: Nice. Could you add tests for this?
JWo1F: @rrousselGit, I've added a test for this behaviour.
JWo1F: I increased the test coverage by adding a check to ensure that everything functions correctly after the value appears.
rrousselGit: Looks like some files are improperly formatted. Could you format them?

Hooks use the master channel, so you might have to switch channel to use the latest formatter
JWo1F: @rrousselGit, seems to be done now, could you check it, please?
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/rrousselGit/flutter_hooks/pull/377?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Remi+Rousselet) Report
Patch coverage: **`100.00%`** and no project coverage change.
> Comparison is base [(`0a6d65a`)](https://app.codecov.io/gh/rrousselGit/flutter_hooks/commit/0a6d65a21f1b1f376c88bfaf8e6918e6b2d7786c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Remi+Rousselet) 99.87% compared to head [(`8b6c42a`)](https://app.codecov.io/gh/rrousselGit/flutter_hooks/pull/377?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Remi+Rousselet) 99.87%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master     #377   +/-   ##
=======================================
  Coverage   99.87%   99.87%           
=======================================
  Files          18       18           
  Lines         810      810           
=======================================
  Hits          809      809           
  Misses          1        1           
```


| [Files Changed](https://app.codecov.io/gh/rrousselGit/flutter_hooks/pull/377?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Remi+Rousselet) | Coverage Δ | |
|---|---|---|
| [...ges/flutter\_hooks/lib/src/listenable\_selector.dart](https://app.codecov.io/gh/rrousselGit/flutter_hooks/pull/377?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Remi+Rousselet#diff-cGFja2FnZXMvZmx1dHRlcl9ob29rcy9saWIvc3JjL2xpc3RlbmFibGVfc2VsZWN0b3IuZGFydA==) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/rrousselGit/flutter_hooks/pull/377?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Remi+Rousselet).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Remi+Rousselet).


Patch:
diff --git a/packages/flutter_hooks/lib/src/listenable_selector.dart b/packages/flutter_hooks/lib/src/listenable_selector.dart
index c73becc..0f0bf42 100644
--- a/packages/flutter_hooks/lib/src/listenable_selector.dart
+++ b/packages/flutter_hooks/lib/src/listenable_selector.dart
@@ -25,7 +25,7 @@ part of 'hooks.dart';
 ///
 
 R useListenableSelector<R>(
-  Listenable listenable,
+  Listenable? listenable,
   R Function() selector,
 ) {
   return use(_ListenableSelectorHook(listenable, selector));
@@ -34,7 +34,7 @@ R useListenableSelector<R>(
 class _ListenableSelectorHook<R> extends Hook<R> {
   const _ListenableSelectorHook(this.listenable, this.selector);
 
-  final Listenable listenable;
+  final Listenable? listenable;
   final R Function() selector;
 
   @override
@@ -49,7 +49,7 @@ class _ListenableSelectorHookState<R>
   @override
   void initHook() {
     super.initHook();
-    hook.listenable.addListener(_listener);
+    hook.listenable?.addListener(_listener);
   }
 
   @override
@@ -63,8 +63,8 @@ class _ListenableSelectorHookState<R>
     }
 
     if (hook.listenable != oldHook.listenable) {
-      oldHook.listenable.removeListener(_listener);
-      hook.listenable.addListener(_listener);
+      oldHook.listenable?.removeListener(_listener);
+      hook.listenable?.addListener(_listener);
       _selectorResult = hook.selector();
     }
   }
@@ -83,7 +83,7 @@ class _ListenableSelectorHookState<R>
 
   @override
   void dispose() {
-    hook.listenable.removeListener(_listener);
+    hook.listenable?.removeListener(_listener);
   }
 
   @override


Test patch:
diff --git a/packages/flutter_hooks/test/use_listenable_selector_test.dart b/packages/flutter_hooks/test/use_listenable_selector_test.dart
index 433d771..99b1499 100644
--- a/packages/flutter_hooks/test/use_listenable_selector_test.dart
+++ b/packages/flutter_hooks/test/use_listenable_selector_test.dart
@@ -85,6 +85,38 @@ void main() {
     listenable.dispose();
   });
 
+  testWidgets('null as Listener', (tester) async {
+    const notFoundValue = -1;
+    final testListener = ValueNotifier(false);
+    final listenable = ValueNotifier(777);
+    var result = 0;
+
+    await tester.pumpWidget(
+      HookBuilder(
+        builder: (context) {
+          final shouldUseListener = useListenableSelector(testListener, () {
+            return testListener.value;
+          });
+
+          final actualListener = shouldUseListener ? listenable : null;
+
+          result = useListenableSelector(actualListener, () {
+            return actualListener?.value ?? notFoundValue;
+          });
+
+          return Container();
+        },
+      ),
+    );
+
+    expect(result, notFoundValue);
+    testListener.value = true;
+
+    await tester.pump();
+
+    expect(result, listenable.value);
+  });
+
   testWidgets('update selector', (tester) async {
     final listenable = ValueNotifier(0);
     var isOdd = false;