Skip to content

Commit

Permalink
Merge pull request #3428 from rrousselGit/pause
Browse files Browse the repository at this point in the history
Add pause mechanism
  • Loading branch information
rrousselGit authored Sep 21, 2024
2 parents 8f0917c + 4e4c4ba commit 36f93a9
Show file tree
Hide file tree
Showing 56 changed files with 1,929 additions and 1,020 deletions.
1 change: 1 addition & 0 deletions packages/flutter_riverpod/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Unreleased build

- **Breaking**: When a ConsumerWidgets stops being visible (based off `Visibility`), it now temporarily stops listening to providers.
- **Breaking**: Removed all `Ref` subclasses (such `FutureProviderRef`).
Use `Ref` directly instead.
For `FutureProviderRef.future`, migrate to using an `AsyncNotifier`.
Expand Down
63 changes: 51 additions & 12 deletions packages/flutter_riverpod/lib/src/core/consumer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ typedef ConsumerBuilder = Widget Function(
/// }
/// ```
///
/// ## Performance considerations
///
/// To optimize performance by avoiding unnecessary network requests and
/// pausing unused streams, [Consumer] will temporarily stop listening to
/// providers when the widget stops being visible.
///
/// This is determined using [Visibility.of], and will invoke
/// [ProviderSubscription.pause] on all currently active subscriptions.
///
/// See also:
///
/// * [ConsumerWidget], a base-class for widgets that wants to listen to providers.
Expand Down Expand Up @@ -349,13 +358,29 @@ class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
/// The [Element] for a [ConsumerStatefulWidget]
ConsumerStatefulElement(ConsumerStatefulWidget super.widget);

@override
BuildContext get context => this;

late ProviderContainer _container = ProviderScope.containerOf(this);
var _dependencies =
<ProviderListenable<Object?>, ProviderSubscription<Object?>>{};
Map<ProviderListenable<Object?>, ProviderSubscription<Object?>>?
_oldDependencies;
final _listeners = <ProviderSubscription<Object?>>[];
List<_ListenManual<Object?>>? _manualListeners;
bool? _visible;

Iterable<ProviderSubscription> get _allSubscriptions sync* {
yield* _dependencies.values;
yield* _listeners;
if (_manualListeners != null) {
yield* _manualListeners!;
}
}

void _applyVisibility(ProviderSubscription sub) {
if (_visible == false) sub.pause();
}

@override
void didChangeDependencies() {
Expand All @@ -372,6 +397,18 @@ class ConsumerStatefulElement extends StatefulElement implements WidgetRef {

@override
Widget build() {
final visible = Visibility.of(context);
if (visible != _visible) {
_visible = visible;
for (final sub in _allSubscriptions) {
if (visible) {
sub.resume();
} else {
sub.pause();
}
}
}

try {
_oldDependencies = _dependencies;
for (var i = 0; i < _listeners.length; i++) {
Expand Down Expand Up @@ -404,10 +441,12 @@ class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
return oldDependency;
}

return _container.listen<Res>(
final sub = _container.listen<Res>(
target,
(_, __) => markNeedsBuild(),
);
_applyVisibility(sub);
return sub;
}).read() as Res;
}

Expand Down Expand Up @@ -448,6 +487,7 @@ class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
// which listen call was preserved between widget rebuild, and we wouldn't
// want to call the listener on every rebuild.
final sub = _container.listen<T>(provider, listener, onError: onError);
_applyVisibility(sub);
_listeners.add(sub);
}

Expand Down Expand Up @@ -493,39 +533,38 @@ class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
final container = ProviderScope.containerOf(this, listen: false);

final sub = _ListenManual(
container,
container.listen(
container.listen<T>(
provider,
listener,
onError: onError,
fireImmediately: fireImmediately,
),
) as ProviderSubscriptionWithOrigin<T, Object?>,
this,
);
_applyVisibility(sub);
listeners.add(sub);

return sub;
}

@override
BuildContext get context => this;
}

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

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

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

@override
T read() => _subscription.read();
T read() => innerSubscription.read();
}
27 changes: 21 additions & 6 deletions packages/flutter_riverpod/test/consumer_listen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,14 @@ void main() {
),
);

expect(container.readProviderElement(provider).hasListeners, true);
expect(container.readProviderElement(provider).hasNonWeakListeners, true);

await tester.pumpWidget(Container());

expect(container.readProviderElement(provider).hasListeners, false);
expect(
container.readProviderElement(provider).hasNonWeakListeners,
false,
);
});

testWidgets('closes the subscription on provider change', (tester) async {
Expand All @@ -180,8 +183,14 @@ void main() {
),
);

expect(container.readProviderElement(provider(0)).hasListeners, true);
expect(container.readProviderElement(provider(1)).hasListeners, false);
expect(
container.readProviderElement(provider(0)).hasNonWeakListeners,
true,
);
expect(
container.readProviderElement(provider(1)).hasNonWeakListeners,
false,
);

await tester.pumpWidget(
UncontrolledProviderScope(
Expand All @@ -195,8 +204,14 @@ void main() {
),
);

expect(container.readProviderElement(provider(0)).hasListeners, false);
expect(container.readProviderElement(provider(1)).hasListeners, true);
expect(
container.readProviderElement(provider(0)).hasNonWeakListeners,
false,
);
expect(
container.readProviderElement(provider(1)).hasNonWeakListeners,
true,
);
});

testWidgets('listen to the new provider on provider change',
Expand Down
Loading

0 comments on commit 36f93a9

Please sign in to comment.