Skip to content

Commit

Permalink
Add mutations (#3860)
Browse files Browse the repository at this point in the history
fixes #1660
  • Loading branch information
rrousselGit authored Dec 8, 2024
1 parent e79523a commit 5ba44ab
Show file tree
Hide file tree
Showing 46 changed files with 4,405 additions and 529 deletions.
45 changes: 15 additions & 30 deletions packages/flutter_riverpod/lib/src/core/consumer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
Map<ProviderListenable<Object?>, ProviderSubscription<Object?>>?
_oldDependencies;
final _listeners = <ProviderSubscription<Object?>>[];
List<_ListenManual<Object?>>? _manualListeners;
List<ProviderSubscription<Object?>>? _manualListeners;
bool? _visible;

Iterable<ProviderSubscription> get _allSubscriptions sync* {
Expand Down Expand Up @@ -532,39 +532,24 @@ class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
// be used inside initState.
final container = ProviderScope.containerOf(this, listen: false);

final sub = _ListenManual(
container.listen<T>(
provider,
listener,
onError: onError,
fireImmediately: fireImmediately,
) as ProviderSubscriptionWithOrigin<T, Object?>,
this,
final innerSubscription = container.listen<T>(
provider,
listener,
onError: onError,
fireImmediately: fireImmediately,
// ignore: invalid_use_of_internal_member, from riverpod
) as ProviderSubscriptionWithOrigin<T, Object?>;

// ignore: invalid_use_of_internal_member, from riverpod
late final ProviderSubscriptionView<T, Object?> sub;
sub = ProviderSubscriptionView<T, Object?>(
innerSubscription: innerSubscription,
onClose: () => _manualListeners?.remove(sub),
read: innerSubscription.read,
);
_applyVisibility(sub);
listeners.add(sub);

return sub;
}
}

final class _ListenManual<T>
// ignore: invalid_use_of_internal_member
extends DelegatingProviderSubscription<T, Object?> {
_ListenManual(this.innerSubscription, this._element);

@override
final ProviderSubscriptionWithOrigin<T, Object?> innerSubscription;
final ConsumerStatefulElement _element;

@override
void close() {
if (!closed) {
_element._manualListeners?.remove(this);
}
super.close();
}

@override
T read() => innerSubscription.read();
}
6 changes: 6 additions & 0 deletions packages/riverpod/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Unreleased build

- **Breaking**: ProviderObserver methods have been updated to take a `ProviderObserverContext` parameter.
This replaces the old `provider`+`container` parameters, and contains extra
information.
- **Breaking**: It is now a runtime exception to "scope" a provider
that is not specifying `dependencies`.
- **Breaking**: Removed all `Ref` subclasses (such `FutureProviderRef`).
Expand All @@ -21,6 +24,9 @@
- **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.
- Added methods to `ProviderObserver` for listening to "mutations".
Mutations are a new code-generation-only feature. See riverpod_generator's changelog
for more information.
- Added `Ref.listen(..., weak: true)`.
When specifying `weak: true`, the listener will not cause the provider to be
initialized. This is useful when wanting to react to changes to a provider,
Expand Down
4 changes: 2 additions & 2 deletions packages/riverpod/lib/riverpod.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ export 'src/framework.dart'
AsyncSubscription,
FutureModifierElement,
RunNotifierBuild,
ProviderListenableWithOrigin,
$FunctionalProvider,
ProviderStateSubscription,
ProviderSubscriptionImpl,
ProviderSubscriptionWithOrigin,
SelectorSubscription,
DelegatingProviderSubscription,
ProviderSubscriptionView,
$ClassProvider,
LegacyProviderMixin,
ClassProviderElement,
Expand Down
6 changes: 5 additions & 1 deletion packages/riverpod/lib/src/common/listenable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class ProxyElementValueListenable<T> extends _ValueListenable<T> {
}

class _ValueListenable<T> {
void Function()? onCancel;

int _count = 0;
// The _listeners is intentionally set to a fixed-length _GrowableList instead
// of const [].
Expand Down Expand Up @@ -93,7 +95,6 @@ class _ValueListenable<T> {
/// [_notifyListeners]; and similarly, by overriding [_removeListener], checking
/// if [hasListeners] is false after calling `super.removeListener()`, and if
/// so, stopping that same work.
@protected
bool get hasListeners {
return _count > 0;
}
Expand Down Expand Up @@ -218,6 +219,9 @@ class _ValueListenable<T> {
break;
}
}

final onCancel = this.onCancel;
if (!hasListeners && onCancel != null) onCancel();
}

/// Discards any resources used by the object. After this is called, the
Expand Down
48 changes: 26 additions & 22 deletions packages/riverpod/lib/src/core/element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ part of '../framework.dart';
/// {@endtemplate}
sealed class Refreshable<StateT> implements ProviderListenable<StateT> {}

mixin _ProviderRefreshable<StateT> implements Refreshable<StateT> {
ProviderBase<Object?> get provider;
mixin _ProviderRefreshable<OutT, OriginT>
implements Refreshable<OutT>, ProviderListenableWithOrigin<OutT, OriginT> {
ProviderBase<OriginT> get provider;
}

/// A debug utility used by `flutter_riverpod`/`hooks_riverpod` to check
Expand Down Expand Up @@ -137,7 +138,6 @@ abstract class ProviderElement<StateT> implements Node {
///
/// This is not meant for public consumption. Instead, public API should use
/// [readSelf].
@internal
Result<StateT>? get stateResult => _stateResult;

/// Returns the currently exposed by a provider
Expand All @@ -156,7 +156,6 @@ abstract class ProviderElement<StateT> implements Node {
///
/// This API is not meant for public consumption. Instead if a [Ref] needs
/// to expose a way to update the state, the practice is to expose a getter/setter.
@internal
void setStateResult(Result<StateT> newState) {
if (kDebugMode) _debugDidSetState = true;

Expand All @@ -175,7 +174,6 @@ abstract class ProviderElement<StateT> implements Node {
///
/// This is not meant for public consumption. Instead, public API should use
/// [readSelf].
@internal
StateT get requireState {
const uninitializedError = '''
Tried to read the state of an uninitialized provider.
Expand Down Expand Up @@ -204,7 +202,6 @@ This could mean a few things:

/// Called when a provider is rebuilt. Used for providers to not notify their
/// listeners if the exposed value did not change.
@internal
bool updateShouldNotify(StateT previous, StateT next);

/* /STATE */
Expand All @@ -225,7 +222,7 @@ This could mean a few things:
}

/// Called the first time a provider is obtained.
void _mount() {
void mount() {
if (kDebugMode) {
_debugCurrentCreateHash = provider.debugGetCreateSourceHash();
}
Expand Down Expand Up @@ -298,11 +295,10 @@ This could mean a few things:
///
/// This is not meant for public consumption. Public API should hide
/// [flush] from users, such that they don't need to care about invoking this function.
@internal
void flush() {
if (!_didMount) {
_didMount = true;
_mount();
mount();
}

_maybeRebuildDependencies();
Expand Down Expand Up @@ -420,6 +416,17 @@ The provider ${_debugCurrentlyBuildingElement!.origin} modified $origin while bu
);
}

MutationContext? _currentMutationContext() =>
Zone.current[mutationZoneKey] as MutationContext?;

ProviderObserverContext _currentObserverContext() {
return ProviderObserverContext(
origin,
container,
mutation: _currentMutationContext(),
);
}

void _notifyListeners(
Result<StateT> newState,
Result<StateT>? previousStateResult, {
Expand Down Expand Up @@ -475,7 +482,7 @@ The provider ${_debugCurrentlyBuildingElement!.origin} modified $origin while bu
if (listener.closed) continue;

Zone.current.runBinaryGuarded(
listener._notify,
listener._onOriginData,
previousState,
newState.state,
);
Expand All @@ -486,7 +493,7 @@ The provider ${_debugCurrentlyBuildingElement!.origin} modified $origin while bu
if (listener.closed) continue;

Zone.current.runBinaryGuarded(
listener._notifyError,
listener._onOriginError,
newState.error,
newState.stackTrace,
);
Expand All @@ -495,31 +502,28 @@ The provider ${_debugCurrentlyBuildingElement!.origin} modified $origin while bu

for (final observer in container.observers) {
if (isMount) {
runTernaryGuarded(
runBinaryGuarded(
observer.didAddProvider,
origin,
_currentObserverContext(),
newState.stateOrNull,
container,
);
} else {
runQuaternaryGuarded(
runTernaryGuarded(
observer.didUpdateProvider,
origin,
_currentObserverContext(),
previousState,
newState.stateOrNull,
container,
);
}
}

for (final observer in container.observers) {
if (newState is ResultError<StateT>) {
runQuaternaryGuarded(
runTernaryGuarded(
observer.providerDidFail,
origin,
_currentObserverContext(),
newState.error,
newState.stackTrace,
container,
);
}
}
Expand Down Expand Up @@ -564,7 +568,7 @@ The provider ${_debugCurrentlyBuildingElement!.origin} modified $origin while bu
);

switch (sub) {
case ProviderSubscriptionImpl():
case final ProviderSubscriptionImpl<Object?, Object?> sub:
sub._listenedElement.addDependentSubscription(sub);
}

Expand Down Expand Up @@ -715,7 +719,7 @@ The provider ${_debugCurrentlyBuildingElement!.origin} modified $origin while bu
ref._onDisposeListeners?.forEach(runGuarded);

for (final observer in container.observers) {
runBinaryGuarded(observer.didDisposeProvider, origin, container);
runUnaryGuarded(observer.didDisposeProvider, _currentObserverContext());
}

ref._keepAliveLinks = null;
Expand Down
32 changes: 25 additions & 7 deletions packages/riverpod/lib/src/core/foundation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,29 @@ String shortHash(Object? object) {
return object.hashCode.toUnsigned(20).toRadixString(16).padLeft(5, '0');
}

@internal
mixin ProviderListenableWithOrigin<OutT, OriginT> on ProviderListenable<OutT> {
@override
ProviderSubscriptionWithOrigin<OutT, OriginT> addListener(
Node source,
void Function(OutT? previous, OutT next) listener, {
required void Function(Object error, StackTrace stackTrace)? onError,
required void Function()? onDependencyMayHaveChanged,
required bool fireImmediately,
required bool weak,
});

@override
ProviderListenable<Selected> select<Selected>(
Selected Function(OutT value) selector,
) {
return _ProviderSelector<OutT, Selected, OriginT>(
provider: this,
selector: selector,
);
}
}

/// A base class for all providers, used to consume a provider.
///
/// It is used by [ProviderContainer.listen] and `ref.watch` to listen to
Expand All @@ -185,7 +208,7 @@ String shortHash(Object? object) {
@immutable
mixin ProviderListenable<StateT> implements ProviderListenableOrFamily {
/// Starts listening to this transformer
ProviderSubscriptionWithOrigin<StateT, Object?> addListener(
ProviderSubscription<StateT> addListener(
Node source,
void Function(StateT? previous, StateT next) listener, {
required void Function(Object error, StackTrace stackTrace)? onError,
Expand Down Expand Up @@ -261,10 +284,5 @@ mixin ProviderListenable<StateT> implements ProviderListenableOrFamily {
/// changed instead of whenever the age changes.
ProviderListenable<Selected> select<Selected>(
Selected Function(StateT value) selector,
) {
return _ProviderSelector<StateT, Selected>(
provider: this,
selector: selector,
);
}
);
}
15 changes: 8 additions & 7 deletions packages/riverpod/lib/src/core/modifiers/future.dart
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,10 @@ base mixin $FutureModifier<StateT> on ProviderBase<AsyncValue<StateT>> {
/// return await http.get('${configs.host}/products');
/// });
/// ```
Refreshable<Future<StateT>> get future {
return ProviderElementProxy<AsyncValue<StateT>, Future<StateT>>(
Refreshable<Future<StateT>> get future => _future;

_ProviderRefreshable<Future<StateT>, AsyncValue<StateT>> get _future {
return ProviderElementProxy<Future<StateT>, AsyncValue<StateT>>(
this,
(element) {
element as FutureModifierElement<StateT>;
Expand Down Expand Up @@ -181,10 +183,10 @@ base mixin $FutureModifier<StateT> on ProviderBase<AsyncValue<StateT>> {
ProviderListenable<Future<Output>> selectAsync<Output>(
Output Function(StateT data) selector,
) {
return _AsyncSelector<StateT, Output>(
return _AsyncSelector<StateT, Output, AsyncValue<StateT>>(
selector: selector,
provider: this,
future: future,
future: _future,
);
}
}
Expand Down Expand Up @@ -290,12 +292,11 @@ mixin FutureModifierElement<StateT> on ProviderElement<AsyncValue<StateT>> {
asyncTransition(value, seamless: seamless);

for (final observer in container.observers) {
runQuaternaryGuarded(
runTernaryGuarded(
observer.providerDidFail,
provider,
_currentObserverContext(),
value.error,
value.stackTrace,
container,
);
}

Expand Down
Loading

0 comments on commit 5ba44ab

Please sign in to comment.