Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to unregister life-cycle listeners #3862

Merged
merged 1 commit into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/riverpod/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
- **Breaking**: A provider is now considered "paused" if all
of its listeners are also paused. So if a provider `A` is watched _only_ by a provider `B`, and `B` is currently unused,
then `A` will be paused.
- All `Ref` life-cycles (such as `Ref.onDispose`) and `Notifier.listenSelf`
now return a function to remove the listener.
- Added methods to `ProviderObserver` for listening to "mutations".
Mutations are a new code-generation-only feature. See riverpod_generator's changelog
for more information.
Expand Down
1 change: 0 additions & 1 deletion packages/riverpod/lib/riverpod.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export 'src/framework.dart'
ProviderElementProxy,
OnError,
ProviderContainerTest,
DebugRiverpodDevtoolBiding,
TransitiveFamilyOverride,
TransitiveProviderOverride,
$ProviderPointer,
Expand Down
17 changes: 0 additions & 17 deletions packages/riverpod/lib/src/core/devtool.dart

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ abstract class NotifierBase<StateT, CreatedT> {
/// As opposed to [Ref.listen], the listener will be called even if
/// [updateShouldNotify] returns false, meaning that the previous
/// and new value can potentially be identical.
///
/// Returns a function which can be called to remove the listener.
@protected
void listenSelf(
RemoveListener listenSelf(
void Function(StateT? previous, StateT next) listener, {
void Function(Object error, StackTrace stackTrace)? onError,
}) {
$ref.listenSelf(listener, onError: onError);
return $ref.listenSelf(listener, onError: onError);
}

@visibleForTesting
Expand Down
5 changes: 0 additions & 5 deletions packages/riverpod/lib/src/core/provider_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,6 @@ class ProviderContainer implements Node {
// This ensures that if an error is thrown, the parent & global state
// are not affected.
parent?._children.add(this);
if (kDebugMode) DebugRiverpodDevtoolBiding.addContainer(this);
}

/// An automatically disposed [ProviderContainer].
Expand Down Expand Up @@ -954,10 +953,6 @@ class ProviderContainer implements Node {
for (final element in getAllProviderElementsInOrder().toList().reversed) {
element.dispose();
}

if (kDebugMode) {
DebugRiverpodDevtoolBiding.removeContainer(this);
}
}

/// Release all the resources associated with this [ProviderContainer].
Expand Down
57 changes: 41 additions & 16 deletions packages/riverpod/lib/src/core/ref.dart
Original file line number Diff line number Diff line change
Expand Up @@ -257,24 +257,32 @@ final <yourProvider> = Provider(dependencies: [<dependency>]);

/// A life-cycle for whenever a new listener is added to the provider.
///
/// Returns a function which can be called to remove the listener.
///
/// See also:
/// - [onRemoveListener], for when a listener is removed
void onAddListener(void Function() cb) {
RemoveListener onAddListener(void Function() cb) {
_throwIfInvalidUsage();

_onAddListeners ??= [];
_onAddListeners!.add(cb);
final list = _onAddListeners ??= [];
list.add(cb);

return () => list.remove(cb);
}

/// A life-cycle for whenever a listener is removed from the provider.
///
/// Returns a function which can be called to remove the listener.
///
/// See also:
/// - [onAddListener], for when a listener is added
void onRemoveListener(void Function() cb) {
RemoveListener onRemoveListener(void Function() cb) {
_throwIfInvalidUsage();

_onRemoveListeners ??= [];
_onRemoveListeners!.add(cb);
final list = _onRemoveListeners ??= [];
list.add(cb);

return () => list.remove(cb);
}

/// Add a listener to perform an operation when the last listener of the provider
Expand All @@ -287,35 +295,43 @@ final <yourProvider> = Provider(dependencies: [<dependency>]);
/// _will_ get paused/dispose. It is possible that after the last listener
/// is removed, a new listener is immediately added.
///
/// Returns a function which can be called to remove the listener.
///
/// See also:
/// - [keepAlive], which can be combined with [onCancel] for
/// advanced manipulation on when the provider should get disposed.
/// - [Provider.autoDispose], a modifier which tell a provider that it should
/// destroy its state when no longer listened to.
/// - [onDispose], a life-cycle for when a provider is disposed.
/// - [onResume], a life-cycle for when the provider is listened to again.
void onCancel(void Function() cb) {
RemoveListener onCancel(void Function() cb) {
_throwIfInvalidUsage();

_onCancelListeners ??= [];
_onCancelListeners!.add(cb);
final list = _onCancelListeners ??= [];
list.add(cb);

return () => list.remove(cb);
}

/// A life-cycle for when a provider is listened again after it was paused
/// (and [onCancel] was triggered).
///
/// Returns a function which can be called to remove the listener.
///
/// See also:
/// - [keepAlive], which can be combined with [onCancel] for
/// advanced manipulation on when the provider should get disposed.
/// - [Provider.autoDispose], a modifier which tell a provider that it should
/// destroy its state when no longer listened to.
/// - [onDispose], a life-cycle for when a provider is disposed.
/// - [onCancel], a life-cycle for when all listeners of a provider are removed.
void onResume(void Function() cb) {
RemoveListener onResume(void Function() cb) {
_throwIfInvalidUsage();

_onResumeListeners ??= [];
_onResumeListeners!.add(cb);
final list = _onResumeListeners ??= [];
list.add(cb);

return () => list.remove(cb);
}

/// Adds a listener to perform an operation right before the provider is destroyed.
Expand Down Expand Up @@ -357,18 +373,22 @@ final <yourProvider> = Provider(dependencies: [<dependency>]);
/// if an exception happens before [onDispose] is called, then
/// some of your objects may not be disposed.
///
/// Returns a function which can be called to remove the listener.
///
/// See also:
///
/// - [Provider.autoDispose], a modifier which tell a provider that it should
/// destroy its state when no longer listened to.
/// - [ProviderContainer.dispose], to destroy all providers associated with
/// a [ProviderContainer] at once.
/// - [onCancel], a life-cycle for when all listeners of a provider are removed.
void onDispose(void Function() listener) {
RemoveListener onDispose(void Function() listener) {
_throwIfInvalidUsage();

_onDisposeListeners ??= [];
_onDisposeListeners!.add(listener);
final list = _onDisposeListeners ??= [];
list.add(listener);

return () => list.remove(listener);
}

/// Read the state associated with a provider, without listening to that provider.
Expand Down Expand Up @@ -611,7 +631,7 @@ class _Ref<StateT> extends Ref {
/// As opposed to [listen], the listener will be called even if
/// [ProviderElement.updateShouldNotify] returns false, meaning that the previous
/// and new value can potentially be identical.
void listenSelf(
RemoveListener listenSelf(
void Function(StateT? previous, StateT next) listener, {
void Function(Object error, StackTrace stackTrace)? onError,
}) {
Expand All @@ -622,6 +642,11 @@ class _Ref<StateT> extends Ref {
_onErrorSelfListeners ??= [];
_onErrorSelfListeners!.add(onError);
}

return () {
_onChangeSelfListeners?.remove(listener);
_onErrorSelfListeners?.remove(onError);
};
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/riverpod/lib/src/framework.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:math' as math;

import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:state_notifier/state_notifier.dart';
import 'package:test/test.dart' as test;
import 'common/env.dart';
import 'common/pragma.dart';
Expand All @@ -27,5 +28,4 @@ part 'core/modifiers/select.dart';
part 'core/scheduler.dart';
part 'core/override_with_value.dart';
part 'core/override.dart';
part 'core/devtool.dart';
part 'core/modifiers/future.dart';
28 changes: 0 additions & 28 deletions packages/riverpod/test/src/core/provider_container_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ TypeMatcher<ProviderDirectory> isProviderDirectory({
}

void main() {
tearDown(() {
// Verifies that there is no container leak.
expect(DebugRiverpodDevtoolBiding.containers, isEmpty);
});

group('ProviderPointerManager', () {
group('findDeepestTransitiveDependencyProviderContainer', () {
final transitiveDependency = Provider(
Expand Down Expand Up @@ -1025,13 +1020,6 @@ void main() {
);
});

test('registers itself in the container list', () {
final container = ProviderContainer();
addTearDown(container.dispose);

expect(DebugRiverpodDevtoolBiding.containers, [container]);
});

test('throws if "parent" is disposed', () {
final root = ProviderContainer();
root.dispose();
Expand All @@ -1046,11 +1034,6 @@ void main() {
isEmpty,
reason: 'Invalid containers should not be added as children',
);
expect(
DebugRiverpodDevtoolBiding.containers,
isEmpty,
reason: 'Invalid containers should not be added to the global list',
);
});

test('if parent is null, assign "root" to "null"', () {
Expand Down Expand Up @@ -1812,17 +1795,6 @@ void main() {
expect(callCount, 0);
});

test('unregister itself from the container list', () {
final container = ProviderContainer();
addTearDown(container.dispose);

expect(DebugRiverpodDevtoolBiding.containers, [container]);

container.dispose();

expect(DebugRiverpodDevtoolBiding.containers, isEmpty);
});

test('Disposes its children first', () {
final rootOnDispose = OnDisposeMock();
final childOnDispose = OnDisposeMock();
Expand Down
Loading
Loading