Skip to content

Commit

Permalink
Revert "Fix multiple root ProviderScopes"
Browse files Browse the repository at this point in the history
This reverts commit 1066baa.
  • Loading branch information
rrousselGit authored Nov 20, 2023
1 parent 1066baa commit 0b015cc
Show file tree
Hide file tree
Showing 10 changed files with 52 additions and 123 deletions.
4 changes: 0 additions & 4 deletions packages/flutter_riverpod/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
## Unreleased fix

Fix exceptions when using multiple root `ProviderContainers`/`ProviderScopes`.

## 2.4.7 - 2023-11-20

- Fix `ProviderObserver.didUpdateProvider` being called with an incorrect
Expand Down
8 changes: 4 additions & 4 deletions packages/flutter_riverpod/lib/src/framework.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:meta/meta.dart';

import 'internals.dart';

/// {@template riverpod.provider_scope}
/// {@template riverpod.providerscope}
/// A widget that stores the state of providers.
///
/// All Flutter applications using Riverpod must contain a [ProviderScope] at
Expand Down Expand Up @@ -78,7 +78,7 @@ import 'internals.dart';
/// {@endtemplate}
@sealed
class ProviderScope extends StatefulWidget {
/// {@macro riverpod.provider_scope}
/// {@macro riverpod.providerscope}
const ProviderScope({
super.key,
this.overrides = const [],
Expand Down Expand Up @@ -305,7 +305,7 @@ class _UncontrolledProviderScopeElement extends InheritedElement {
debugCanModifyProviders ??= _debugCanModifyProviders;
}

_containerOf(widget).scheduler.flutterVsyncs.add(_flutterVsync);
flutterVsyncs.add(_flutterVsync);
super.mount(parent, newSlot);
}

Expand Down Expand Up @@ -380,7 +380,7 @@ To fix this problem, you have one of two solutions:
debugCanModifyProviders = null;
}

_containerOf(widget).scheduler.flutterVsyncs.remove(_flutterVsync);
flutterVsyncs.remove(_flutterVsync);

super.unmount();
}
Expand Down
64 changes: 8 additions & 56 deletions packages/flutter_riverpod/test/framework_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,48 +47,6 @@ void main() {
);
});

testWidgets('Supports multiple ProviderScope roots in the same tree',
(tester) async {
final a = StateProvider((_) => 0);
final b = Provider((ref) => ref.watch(a));

await tester.pumpWidget(
// No root scope. We want to test cases where there are multiple roots
Column(
mainAxisSize: MainAxisSize.min,
children: [
for (var i = 0; i < 2; i++)
SizedBox(
width: 100,
height: 100,
child: ProviderScope(
child: Consumer(
builder: (context, ref, _) {
ref.watch(a);
ref.watch(b);
return Container();
},
),
),
),
],
),
);

final containers = tester.allElements
.where((e) => e.widget is Consumer)
.map(ProviderScope.containerOf)
.toList();

expect(containers, hasLength(2));

for (final container in containers) {
container.read(a.notifier).state++;
}

await tester.pump();
});

testWidgets('ref.invalidate can invalidate a family', (tester) async {
final listener = Listener<String>();
final listener2 = Listener<String>();
Expand Down Expand Up @@ -326,9 +284,8 @@ void main() {
testWidgets('UncontrolledProviderScope gracefully handles vsync',
(tester) async {
final container = createContainer();
final container2 = createContainer(parent: container);

expect(container.scheduler.flutterVsyncs, isEmpty);
expect(flutterVsyncs, isEmpty);

await tester.pumpWidget(
UncontrolledProviderScope(
Expand All @@ -337,21 +294,18 @@ void main() {
),
);

expect(container.scheduler.flutterVsyncs, hasLength(1));
expect(container2.scheduler.flutterVsyncs, isEmpty);
expect(flutterVsyncs, hasLength(1));

await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: UncontrolledProviderScope(
container: container2,
child: ProviderScope(
child: Container(),
),
),
);

expect(container.scheduler.flutterVsyncs, hasLength(1));
expect(container2.scheduler.flutterVsyncs, hasLength(1));
expect(flutterVsyncs, hasLength(2));

await tester.pumpWidget(
UncontrolledProviderScope(
Expand All @@ -360,13 +314,11 @@ void main() {
),
);

expect(container.scheduler.flutterVsyncs, hasLength(1));
expect(container2.scheduler.flutterVsyncs, isEmpty);
expect(flutterVsyncs, hasLength(1));

await tester.pumpWidget(Container());

expect(container.scheduler.flutterVsyncs, isEmpty);
expect(container2.scheduler.flutterVsyncs, isEmpty);
expect(flutterVsyncs, isEmpty);
});

testWidgets('When there are multiple vsyncs, rebuild providers only once',
Expand Down Expand Up @@ -673,7 +625,7 @@ void main() {
await tester.pumpWidget(
ProviderScope(
overrides: [
provider.overrideWithValue('rootOverride'),
provider.overrideWithValue('rootoverride'),
],
child: ProviderScope(
child: Consumer(
Expand All @@ -691,7 +643,7 @@ void main() {
);

expect(find.text('root root2'), findsNothing);
expect(find.text('rootOverride root2'), findsOneWidget);
expect(find.text('rootoverride root2'), findsOneWidget);
});

testWidgets('ProviderScope throws if ancestorOwner changed', (tester) async {
Expand Down
4 changes: 0 additions & 4 deletions packages/hooks_riverpod/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
## Unreleased fix

Fix exceptions when using multiple root `ProviderContainers`/`ProviderScopes`.

## 2.4.7 - 2023-11-20

- Fix `ProviderObserver.didUpdateProvider` being called with an incorrect
Expand Down
4 changes: 0 additions & 4 deletions packages/riverpod/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
## Unreleased fix

Fix exceptions when using multiple root `ProviderContainers`/`ProviderScopes`.

## 2.4.7 - 2023-11-20

- Fix `ProviderObserver.didUpdateProvider` being called with an incorrect
Expand Down
3 changes: 2 additions & 1 deletion packages/riverpod/lib/riverpod.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export 'src/common.dart' hide AsyncTransition;

export 'src/framework.dart'
hide
ProviderScheduler,
debugCanModifyProviders,
vsync,
flutterVsyncs,
Vsync,
ValueProviderElement,
ValueProvider,
Expand Down
2 changes: 1 addition & 1 deletion packages/riverpod/lib/src/framework/auto_dispose.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ mixin AutoDisposeProviderElementMixin<State> on ProviderElementBase<State>

// ignore: deprecated_member_use_from_same_package
if (!maintainState && !hasListeners && (links == null || links.isEmpty)) {
_container.scheduler.scheduleProviderDispose(this);
_container._scheduler.scheduleProviderDispose(this);
}
}

Expand Down
19 changes: 4 additions & 15 deletions packages/riverpod/lib/src/framework/container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ class ProviderContainer implements Node {
void Function(void Function() task)? vsyncOverride;

/// The object that handles when providers are refreshed and disposed.
@internal
late final ProviderScheduler scheduler = ProviderScheduler();
late final _ProviderScheduler _scheduler =
_parent?._scheduler ?? _ProviderScheduler();

/// How deep this [ProviderContainer] is in the graph of containers.
///
Expand Down Expand Up @@ -214,13 +214,7 @@ class ProviderContainer implements Node {

/// Awaits for providers to rebuild/be disposed and for listeners to be notified.
Future<void> pump() async {
final a = scheduler.pendingFuture;
final b = _parent?.scheduler.pendingFuture;

await Future.wait<void>([
if (a != null) a,
if (b != null) b,
]);
return _scheduler.pendingFuture;
}

/// Reads a provider without listening to it and returns the currently
Expand Down Expand Up @@ -636,7 +630,7 @@ final b = Provider((ref) => ref.watch(a), dependencies: [a]);
element.dispose();
}

if (_root == null) scheduler.dispose();
if (_root == null) _scheduler.dispose();
}

/// Traverse the [ProviderElementBase]s associated with this [ProviderContainer].
Expand Down Expand Up @@ -787,11 +781,6 @@ class ProviderOverride implements Override {

/// The new provider behavior.
final ProviderBase<Object?> _override;

@override
String toString() {
return 'ProviderOverride(${_origin.name} with ${(_override as ValueProvider)._value})';
}
}

/// An object used by [ProviderContainer]/`ProviderScope` to override the behavior
Expand Down
2 changes: 1 addition & 1 deletion packages/riverpod/lib/src/framework/element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ abstract class ProviderElementBase<State> implements Ref<State>, Node {

_mustRecomputeState = true;
runOnDispose();
_container.scheduler.scheduleProviderRefresh(this);
_container._scheduler.scheduleProviderRefresh(this);

// We don't call this._markDependencyMayHaveChanged here because we voluntarily
// do not want to set the _dependencyMayHaveChanged flag to true.
Expand Down
65 changes: 32 additions & 33 deletions packages/riverpod/lib/src/framework/scheduler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,48 @@ part of '../framework.dart';
@internal
typedef Vsync = void Function(void Function());

/// A way to override [vsync], used by Flutter to synchronize a container
/// with the widget tree.
@internal
final flutterVsyncs = <Vsync>{};

void _defaultVsync(void Function() task) {
Future(task);
}

/// A function that controls the refresh rate of providers.
///
/// Defaults to refreshing providers at the end of the next event-loop.
@internal
void Function(void Function()) get vsync {
if (flutterVsyncs.isNotEmpty) {
// Notify all InheritedWidgets of a possible rebuild.
// At the same time, we only execute the task once, in whichever
// InheritedWidget that rebuilds first.
return (task) {
var invoked = false;
void invoke() {
if (invoked) return;
invoked = true;
task();
}

for (final flutterVsync in flutterVsyncs) {
flutterVsync(invoke);
}
};
}

return _defaultVsync;
}

/// The object that handles when providers are refreshed and disposed.
///
/// Providers are typically refreshed at the end of the frame where they
/// notified that they wanted to rebuild.
///
/// Providers are disposed if they spent at least one full frame without any listener.
@internal
class ProviderScheduler {
/// A way to override [vsync], used by Flutter to synchronize a container
/// with the widget tree.
@internal
final flutterVsyncs = <Vsync>{};

/// A function that controls the refresh rate of providers.
///
/// Defaults to refreshing providers at the end of the next event-loop.
@internal
void Function(void Function()) get vsync {
if (flutterVsyncs.isNotEmpty) {
// Notify all InheritedWidgets of a possible rebuild.
// At the same time, we only execute the task once, in whichever
// InheritedWidget that rebuilds first.
return (task) {
var invoked = false;
void invoke() {
if (invoked) return;
invoked = true;
task();
}

for (final flutterVsync in flutterVsyncs) {
flutterVsync(invoke);
}
};
}

return _defaultVsync;
}

class _ProviderScheduler {
final _stateToDispose = <AutoDisposeProviderElementMixin<Object?>>[];
final _stateToRefresh = <ProviderElementBase<Object?>>[];

Expand Down

0 comments on commit 0b015cc

Please sign in to comment.