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

Invalidating an AutoDisposeProvider A that watches AutoDisposeProvider B doesn't dispose AutoDisposeProvider B automatically. #3455

Open
aldee opened this issue Mar 28, 2024 · 2 comments
Assignees
Labels
bug Something isn't working needs triage

Comments

@aldee
Copy link

aldee commented Mar 28, 2024

Describe the bug
Let's say I have a Widget that depends on FutureProvider A, and then FutureProvider A depends on FutureProvider B. The Widget doesn't know about FutureProvider B. If FutureProvider B returns an error, FutureProvider A will return an error as well to the Widget. When the Widget got an Error, it would show an option to refresh by calling WidgetRef.invalidate() on FutureProvider A.

I assumed when FutureProvider A got invalidated, it would get disposed, then since only FutureProvider A depends on/watches FutureProvider B, FutureProvider B would get disposed of as well, resulting in a recomputed value for FutureProvider B.

But in Riverpod 2.5.0 that I'm using in my project, it still return the previous error/value.

To Reproduce

This is the code that I use to reproduce it:

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

const wholeSentence = 'a sentence + $number';
const number = 100;

void main() {
  test('testing future provider', () async {
    final container = createContainer();

    // First throw an exception
    container.updateOverrides([listenedProvider.overrideWith((ref) => throw Exception())]);
    await expectLater(
      container.read(listeningProvider.future),
      throwsA(isA<Exception>()),
    );

    // Then expect to get new value after invalidate
    container.updateOverrides([listenedProvider.overrideWith((ref) async => number)]);
    container.invalidate(listeningProvider);
    await expectLater(
      container.read(listeningProvider.future),
      completion(wholeSentence),
    );
  });

  testWidgets('testing widget with future provider', (widgetTester) async {
    await widgetTester.pumpWidget(
      ProviderScope(
        overrides: [
          listenedProvider.overrideWith((ref) {
            print('throwing Exception');
            throw Exception();
          }),
        ],
        child: const SomeWidget(),
      ),
    );

    print('setting up container');
    final element = widgetTester.element(find.byType(SomeWidget));
    final container = ProviderScope.containerOf(element);

    print('running first test');
    await expectLater(
      container.read(listeningProvider.future),
      throwsA(isA<Exception>()),
    );

    print('invalidate then get new value from listenedProvider');
    // container.invalidate(listenedProvider);
    container.invalidate(listeningProvider);
    container.updateOverrides([listenedProvider.overrideWith((ref) async => number)]);
    await widgetTester.pumpAndSettle();
    await expectLater(
      container.read(listeningProvider.future),
      completion(wholeSentence),
    );
  });
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final result = ref.watch(listeningProvider);
    if (result.hasValue) {
      return MaterialApp(home: Text(result.value!));
    }
    return Container();
  }
}

final listenedProvider = FutureProvider.autoDispose<int>((ref) async {
  throw UnimplementedError();
});

final listeningProvider = FutureProvider.autoDispose<String>((ref) async {
  final number = await ref.watch(listenedProvider.future);
  return 'a sentence + $number';
});

ProviderContainer createContainer() {
  final container = ProviderContainer(
    overrides: [
      listenedProvider.overrideWith((ref) async {
        return number;
      }),
    ],
  );
  addTearDown(() => container.dispose());

  return container;
}

Expected behavior
I expected both the unit test and widget test above should pass successfully. But in reality, it still throws an error. Only when I added ref.invalidate(listenedProvider) would it pass. But I don't want that, since it shouldn't know about listenedProvider/FutureProvider B

@aldee aldee added bug Something isn't working needs triage labels Mar 28, 2024
@aldee
Copy link
Author

aldee commented Apr 1, 2024

pinging @rrousselGit in case you missed this one. is this an intended behaviour? if yes, what's the appropriate approach for that goal?

@nateshmbhat
Copy link

facing same issue. what's the recommended way to handle this ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs triage
Projects
None yet
Development

No branches or pull requests

3 participants