Repository: rrousselGit/riverpod

Issue:
Allow `ref.invalidate(family)`




Fix patch:
diff --git a/packages/flutter_riverpod/lib/src/consumer.dart b/packages/flutter_riverpod/lib/src/consumer.dart
index 1f0fc40be5..def0327cd9 100644
--- a/packages/flutter_riverpod/lib/src/consumer.dart
+++ b/packages/flutter_riverpod/lib/src/consumer.dart
@@ -176,7 +176,7 @@ abstract class WidgetRef {
   /// once.
   ///
   /// Calling [invalidate] will cause the provider to be disposed immediately.
-  void invalidate(ProviderBase<Object?> provider);
+  void invalidate(ProviderOrFamily provider);
 }
 
 /// A function that can also listen to providers
@@ -555,7 +555,7 @@ class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
   }
 
   @override
-  void invalidate(ProviderBase<Object?> provider) {
+  void invalidate(ProviderOrFamily provider) {
     _container.invalidate(provider);
   }
 
diff --git a/packages/riverpod/CHANGELOG.md b/packages/riverpod/CHANGELOG.md
index 2684a81631..2fbe24f3c7 100644
--- a/packages/riverpod/CHANGELOG.md
+++ b/packages/riverpod/CHANGELOG.md
@@ -1,6 +1,7 @@
-# Unreleased fix
+# Unreleased minor
 
-Fixes StateProvider.state not respecting the `name` parameter (#1470)
+- fix: StateProvider.state not respecting the `name` parameter (#1470)
+- feat: Add support for `ref.invalidate(family)` to recompiute an entire family (#1517)
 
 # 2.0.0-dev.9
 
diff --git a/packages/riverpod/lib/src/framework/container.dart b/packages/riverpod/lib/src/framework/container.dart
index bd1976ca37..35ec70a9ae 100644
--- a/packages/riverpod/lib/src/framework/container.dart
+++ b/packages/riverpod/lib/src/framework/container.dart
@@ -349,12 +349,21 @@ class ProviderContainer implements Node {
   }
 
   /// {@template riverpod.invalidate}
-  void invalidate(ProviderBase<Object?> provider) {
-    final reader = _getStateReader(provider.originProvider);
+  void invalidate(ProviderOrFamily provider) {
+    if (provider is ProviderBase) {
+      final reader = _getStateReader(provider.originProvider);
 
-    if (reader._element != null) {
-      final element = reader._element!;
-      element.invalidateSelf();
+      reader._element?.invalidateSelf();
+    } else {
+      provider as Family;
+
+      final familyContainer =
+          _overrideForFamily[provider]?.container ?? _root ?? this;
+
+      for (final stateReader in familyContainer._stateReaders.values) {
+        if (stateReader.origin.from != provider) continue;
+        stateReader._element?.invalidateSelf();
+      }
     }
   }
 
diff --git a/packages/riverpod/lib/src/framework/provider_base.dart b/packages/riverpod/lib/src/framework/provider_base.dart
index 173a85aa10..ec913ab99e 100644
--- a/packages/riverpod/lib/src/framework/provider_base.dart
+++ b/packages/riverpod/lib/src/framework/provider_base.dart
@@ -394,7 +394,7 @@ abstract class ProviderElementBase<State> implements Ref<State>, Node {
   }
 
   @override
-  void invalidate(ProviderBase<Object?> provider) {
+  void invalidate(ProviderOrFamily provider) {
     _container.invalidate(provider);
   }
 
diff --git a/packages/riverpod/lib/src/framework/ref.dart b/packages/riverpod/lib/src/framework/ref.dart
index 662c6c1d6f..a8a3a2cbb0 100644
--- a/packages/riverpod/lib/src/framework/ref.dart
+++ b/packages/riverpod/lib/src/framework/ref.dart
@@ -37,7 +37,7 @@ abstract class Ref<State extends Object?> {
   ///
   /// Calling [invalidate] will cause the provider to be disposed immediately.
   /// {@endtemplate}
-  void invalidate(ProviderBase<Object?> provider);
+  void invalidate(ProviderOrFamily provider);
 
   /// Listens to changes on the value exposed by this provider
   ///


Test patch:
diff --git a/packages/flutter_riverpod/test/framework_test.dart b/packages/flutter_riverpod/test/framework_test.dart
index 4a983e558a..6e03364fc0 100644
--- a/packages/flutter_riverpod/test/framework_test.dart
+++ b/packages/flutter_riverpod/test/framework_test.dart
@@ -8,6 +8,39 @@ import 'package:mockito/mockito.dart';
 import 'utils.dart';
 
 void main() {
+  testWidgets('ref.invalidate can invalidate a family', (tester) async {
+    final listener = Listener<String>();
+    final listener2 = Listener<String>();
+    var result = 0;
+    final provider = Provider.family<String, int>((r, i) => '$result-$i');
+    late WidgetRef ref;
+
+    await tester.pumpWidget(
+      ProviderScope(
+        child: Consumer(
+          builder: (context, r, _) {
+            ref = r;
+            ref.listen(provider(0), listener);
+            ref.listen(provider(1), listener2);
+            return Container();
+          },
+        ),
+      ),
+    );
+
+    verifyZeroInteractions(listener);
+
+    ref.invalidate(provider);
+    result = 1;
+
+    verifyZeroInteractions(listener);
+
+    await tester.pumpAndSettle();
+
+    verifyOnly(listener, listener('0-0', '1-0'));
+    verifyOnly(listener2, listener2('0-1', '1-1'));
+  });
+
   testWidgets('ref.invalidate triggers a rebuild on next frame',
       (tester) async {
     final listener = Listener<int>();
diff --git a/packages/riverpod/test/framework/provider_element_test.dart b/packages/riverpod/test/framework/provider_element_test.dart
index 542d6fa306..22eb485d83 100644
--- a/packages/riverpod/test/framework/provider_element_test.dart
+++ b/packages/riverpod/test/framework/provider_element_test.dart
@@ -9,6 +9,80 @@ import '../third_party/fake_async.dart';
 import '../utils.dart';
 
 void main() {
+  group('ref.invalidate on families', () {
+    test('recomputes providers associated with the family', () async {
+      final container = createContainer();
+      final listener = Listener<String>();
+      final listener2 = Listener<String>();
+      final listener3 = Listener<int>();
+      var result = 0;
+      final unrelated = Provider((ref) => result);
+      final provider = Provider.family<String, int>((r, i) => '$result-$i');
+      late Ref ref;
+      final another = Provider((r) {
+        ref = r;
+      });
+
+      container.read(another);
+
+      container.listen(provider(0), listener, fireImmediately: true);
+      container.listen(provider(1), listener2, fireImmediately: true);
+      container.listen(unrelated, listener3, fireImmediately: true);
+
+      verifyOnly(listener, listener(null, '0-0'));
+      verifyOnly(listener2, listener2(null, '0-1'));
+      verifyOnly(listener3, listener3(null, 0));
+
+      ref.invalidate(provider);
+      ref.invalidate(provider);
+      result = 1;
+
+      verifyNoMoreInteractions(listener);
+      verifyNoMoreInteractions(listener2);
+      verifyNoMoreInteractions(listener3);
+
+      await container.pump();
+
+      verifyOnly(listener, listener('0-0', '1-0'));
+      verifyOnly(listener2, listener2('0-1', '1-1'));
+      verifyNoMoreInteractions(listener3);
+    });
+
+    test('clears only on the closest family override', () async {
+      late Ref ref;
+      final another = Provider((r) {
+        ref = r;
+      });
+      var result = 0;
+      final provider = Provider.family<int, int>((r, i) => result);
+      final listener = Listener<int>();
+      final listener2 = Listener<int>();
+      final root = createContainer();
+      final container = createContainer(
+        parent: root,
+        overrides: [provider, another],
+      );
+
+      container.read(another);
+      root.listen(provider(0), listener, fireImmediately: true);
+      container.listen(provider(1), listener2, fireImmediately: true);
+
+      verifyOnly(listener, listener(null, 0));
+      verifyOnly(listener2, listener2(null, 0));
+
+      ref.invalidate(provider);
+      result = 1;
+
+      verifyNoMoreInteractions(listener);
+      verifyNoMoreInteractions(listener2);
+
+      await container.pump();
+
+      verifyOnly(listener2, listener2(0, 1));
+      verifyNoMoreInteractions(listener);
+    });
+  });
+
   test('ref.invalidate triggers a rebuild on next frame', () async {
     final container = createContainer();
     final listener = Listener<int>();