riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

Tried to read [Provider] from a place where one of its dependencies were overridden but the provider is not. When using 2 ProviderScope

Open GP4cK opened this issue 2 years ago • 9 comments

Hello

Describe the bug When trying to override a provider in tests, if I have 2 ProviderScope in my widget tree, I get the error message:

Tried to read AutoDisposeProvider<String>#cc8cc(hello world) from a place where one of its
dependencies were overridden but the provider is not.

To fix this error, you can add add "dependencies" to AutoDisposeProvider<String>#cc8cc(hello world)
such that we have:

final a = Provider(...);
final b = Provider((ref) => ref.watch(a), dependencies: [a]);

To Reproduce

Here's the code:

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final a = Provider.family.autoDispose<String, String>((ref, value) {
  return value;
});

final b = Provider.family.autoDispose<String, String>((ref, value) {
  return ref.watch(a(value));
}, dependencies: [a]);

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      home: Text(ref.watch(b('hello world'))),
    );
  }
}

// widget_test.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod_family_dependencies/main.dart';

void main() {
  testWidgets('Test scoping provider', (WidgetTester tester) async {
    const text = 'please help me Remi';
    await tester.pumpWidget(
      ProviderScope( // If I remove this one, then the test will pass
        child: ProviderScope(
          overrides: [a('hello world').overrideWithValue(text)],
          child: const MyApp(),
        ),
      ),
    );
    expect(find.text(text), findsOneWidget);
  });
}

I've created a sample repo with 2 branches which both have the issue.

  • main is using ^1.0.4
  • 2.1.1 is using... ^2.1.1 https://github.com/GP4cK/riverpod_family_dependencies

Expected behavior I would expect to be able to override a provider even if I have multiple ProviderScope in the widget tree as long as the ProviderScope that does the override is higher than the widget which is reading the provider.

GP4cK avatar Nov 03 '22 03:11 GP4cK

BTW I created a mini PR to remove the double "add" in the error message: https://github.com/rrousselGit/riverpod/pull/1870

GP4cK avatar Nov 03 '22 03:11 GP4cK

I also did 2 additional tests with overrideWith. The difference is in the way I override a. So it must be an issue with family. But I struggle to understand the code of readProviderElement and what the assert is checking :/

// This passes:
testWidgets('test scoping family provider with overrideWith', (tester) async {
  const text = 'hello riverpod';
  const family = 'hello world';
  final a =
      Provider.family.autoDispose<String, String>((ref, value) => value);

  final b = Provider.family.autoDispose<String, String>(
    (ref, value) => ref.watch(a(value)),
    dependencies: [a],
  );

  await tester.pumpWidget(
    ProviderScope(
      child: ProviderScope(
        overrides: [a.overrideWith((ref, value) => text)], // Overrides `a` entirely.
        child: Consumer(
          builder: (_, ref, __) => Text(
            ref.watch(b(family)),
            textDirection: TextDirection.ltr,
          ),
        ),
      ),
    ),
  );
  expect(find.text(text), findsOneWidget);
});

// This fails:
testWidgets('test scoping a provider with overrideWith', (tester) async {
  const text = 'hello riverpod';
  const family = 'hello world';
  final a =
      Provider.family.autoDispose<String, String>((ref, value) => value);

  final b = Provider.family.autoDispose<String, String>(
    (ref, value) => ref.watch(a(value)),
    dependencies: [a],
  );

  await tester.pumpWidget(
    ProviderScope(
      child: ProviderScope(
        overrides: [a(family).overrideWith((ref) => text)], // Only override a(family).
        child: Consumer(
          builder: (_, ref, __) => Text(
            ref.watch(b(family)),
            textDirection: TextDirection.ltr,
          ),
        ),
      ),
    ),
  );
  expect(find.text(text), findsOneWidget);
});

GP4cK avatar Nov 06 '22 02:11 GP4cK

Thanks for the detailed report.

rrousselGit avatar Nov 08 '22 12:11 rrousselGit

It seems this example is fixed in version 2.3.2. The test is passing with 2 ProviderScopes

ValentinVignal avatar Apr 04 '23 10:04 ValentinVignal

Mmm I created a branch with 2.3.2 and the tests still fail for me...

GP4cK avatar Apr 04 '23 11:04 GP4cK

Any update on this?

imtoori avatar Nov 29 '23 09:11 imtoori

I can reproduce. I'll try to fix it this week

rrousselGit avatar Nov 29 '23 09:11 rrousselGit

This appears specific to overrides: [family('arg').overrideWith(...)]

Doing overrides: [family.overrideWith((ref, arg) => '...')] works fine.

rrousselGit avatar Nov 29 '23 09:11 rrousselGit

I found the problem, but this is quite the complex problem to fix. I have some ideas on a solution, but for now I'd suggest using the workaround mentioned above.

I'm probably not going to fix this before 3.0 as it requires a few internal changes and is fairly minor with a workaround.

rrousselGit avatar Nov 29 '23 11:11 rrousselGit