Skip to content

Commit 2e4a090

Browse files
committed
Add manual invalidation support to provider invalidation logic
1 parent e35c43c commit 2e4a090

File tree

5 files changed

+344
-6
lines changed

5 files changed

+344
-6
lines changed

packages/riverpod/lib/src/core/element.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ depending on itself.
514514
_debugCurrentCreateHash = provider.debugGetCreateSourceHash();
515515

516516
if (previousHash != _debugCurrentCreateHash) {
517-
invalidateSelf(asReload: false);
517+
invalidateSelf(asReload: false, manual: false);
518518
}
519519
}
520520

@@ -714,7 +714,7 @@ depending on itself.
714714
_pendingRetryTimer = Timer(duration, () {
715715
_pendingRetryTimer = null;
716716
_retryCount++;
717-
invalidateSelf(asReload: false);
717+
invalidateSelf(asReload: false, manual: false);
718718
});
719719
});
720720
}
@@ -746,13 +746,22 @@ The provider ${_debugCurrentlyBuildingElement!.origin} modified $origin while bu
746746
debugCanModifyProviders?.call();
747747
}
748748

749-
void invalidateSelf({required bool asReload}) {
749+
void invalidateSelf({required bool asReload, required bool manual}) {
750750
if (!_didMount) return;
751751

752752
if (asReload) _didChangeDependency = true;
753753
if (_mustRecomputeState) return;
754754

755755
_mustRecomputeState = true;
756+
757+
// Call manual invalidation listeners before runOnDispose clears them
758+
if (manual) {
759+
final onManualInvalidationListeners = ref?._onManualInvalidationListeners;
760+
if (onManualInvalidationListeners != null) {
761+
_runCallbacks(container, onManualInvalidationListeners);
762+
}
763+
}
764+
756765
runOnDispose();
757766
mayNeedDispose();
758767
container.scheduler.scheduleProviderRefresh(this);
@@ -1193,6 +1202,7 @@ $this''',
11931202
ref._onRemoveListeners = null;
11941203
ref._onChangeSelfListeners = null;
11951204
ref._onErrorSelfListeners = null;
1205+
ref._onManualInvalidationListeners = null;
11961206
_didCancelOnce = false;
11971207

11981208
assert(

packages/riverpod/lib/src/core/provider_container.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -977,10 +977,10 @@ final class ProviderContainer implements Node, MutationTarget {
977977
case ProviderBase<Object?>():
978978
_pointerManager
979979
.readElement(provider)
980-
?.invalidateSelf(asReload: asReload);
980+
?.invalidateSelf(asReload: asReload, manual: !asReload);
981981
case Family():
982982
for (final element in _pointerManager.listFamily(provider)) {
983-
element.invalidateSelf(asReload: asReload);
983+
element.invalidateSelf(asReload: asReload, manual: !asReload);
984984
}
985985
}
986986
}

packages/riverpod/lib/src/core/ref.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ sealed class Ref implements MutationTarget {
5050
List<void Function()>? _onCancelListeners;
5151
List<void Function()>? _onAddListeners;
5252
List<void Function()>? _onRemoveListeners;
53+
List<void Function()>? _onManualInvalidationListeners;
5354

5455
/// Whether we're initializing this provider for the first time.
5556
///
@@ -332,7 +333,36 @@ final <yourProvider> = Provider(dependencies: [<dependency>]);
332333
void invalidateSelf({bool asReload = false}) {
333334
_throwIfInvalidUsage();
334335

335-
_element.invalidateSelf(asReload: asReload);
336+
_element.invalidateSelf(asReload: asReload, manual: !asReload);
337+
}
338+
339+
/// {@template riverpod.onManualInvalidation}
340+
/// A life-cycle for whenever this provider is manually invalidated, as opposed
341+
/// to automatic invalidation caused by dependency changes.
342+
///
343+
/// This callback is triggered when:
344+
/// - [invalidateSelf] with `asReload: false` (the default)
345+
/// - [invalidate] with `asReload: false` (the default) on this provider
346+
/// - [refresh] on this provider
347+
///
348+
/// This callback is NOT triggered when:
349+
/// - A dependency watched with [watch] changes
350+
/// - [invalidateSelf] or [invalidate] with `asReload: true`
351+
///
352+
/// Returns a function which can be called to remove the listener.
353+
///
354+
/// See also:
355+
/// - [invalidateSelf], to invalidate this provider
356+
/// - [refresh], to forcefully re-evaluate a provider
357+
/// {@endtemplate}
358+
@experimental
359+
RemoveListener onManualInvalidation(void Function() cb) {
360+
_throwIfInvalidUsage();
361+
362+
final list = _onManualInvalidationListeners ??= [];
363+
list.add(cb);
364+
365+
return () => list.remove(cb);
336366
}
337367

338368
/// Notify dependents that this provider has changed.

0 commit comments

Comments
 (0)