rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.24k stars 955 forks source link

AutoDisposeStateNotifierProvider doesn't dispose when another provider maintainState value is true #1332

Closed nank1ro closed 2 years ago

nank1ro commented 2 years ago

Describe the bug I don't know if this is really a bug, or the behaviour is intentional. I'm here more to have a clarification.

Note: I'm using flutter_riverpod version ^1.0.3

Let's say that I have an autodispose state notifier provider, like this:

final someBLoCProvider = AutoDisposeStateNotifierProvider<SomeBLoC, int>((ref) {
  ref.onDispose(() {
    print("never disposed");
  });
  return SomeBLoC();
});

Then I have an autodispose future provider that takes the future from the previous bloc, like:

final someFutureProvider = AutoDisposeFutureProvider<String>((ref) async {
  final result = await ref.watch(someBLoCProvider.notifier).someFuture();
  ref.maintainState = true;
  return result;
});

As you can see, the future provider maintains its state after performing successfully the future.

Whenever I go to Page2 of my app, I perform the future (only the first time) and show the result. If I go again to Page2, the result is already displayed, and the future is not repeated. This works as expected. Unfortunately, when popping Page2 I would expect that someBLoCProvider will get disposed, but this never happens.

Seems like that someFutureProvider still listens to someBLoCProvider preventing it from disposing. If this is the expected behavior, how can I be sure that someBLoCProvider is disposed when going back to Page1?

Right now, the only thing that worked is by removing ref.maintainState = true, but in this way I'm losing the "cache" of the future.

To Reproduce I have created a sample repo to reproduce the issue here: https://github.com/nank1ro/riverpod_103_bug if you want to reproduce it.

Expected behavior When a future inside a provider has been completed and maintains its state, it should not keep alive other providers.

rrousselGit commented 2 years ago

That's expected.

The provider is being listened, so it won't be disposed.

As for the alternative, hard to say without knowing what you're trying to do (as what you've described is your attempted solution, not the problem)

nank1ro commented 2 years ago

I simply want to cache the future result, but dispose the bloc when not in use.

I'm thinking that the real problem is that the Future is inside the bloc. In the Riverpod ideal architecture you would keep it completely separated and handle all the logic in the provider, right?

nank1ro commented 2 years ago

To give you a more real example: I have a quiz app, with an ExerciseBLoC that offers a method downloadExercise(int index).

The ExerciseBLoC is a state notifier and keeps track of each question state (uncompleted, wrong, correct). Finally I have an ExercisePage that uses this bloc to show the exercises and each time a user completes a question, the downloadExercise future is called with a new exercise index.

I would like to cache the result of the future, to avoid making another API call when the same question is shown twice. In addition, when the ExercisePage is popped I want to have the ExerciseBLoC to be disposed. In this way I can prevent the previous state to be already present when reopening the page.

rrousselGit commented 2 years ago

This isn't a bug. The problem is more an architectural one

You likely want to split your "Bloc". Maybe you want to extract the someFuture in a separate class that can safely not be disposed (a Repository?).