Closed talent-apps closed 2 years ago
Well ultimately you can do that yourself already
Provider.autoDispose((ref) { ref.keepAlive(); ref.onCancel(ref.invalidateSelf); } This effectively produces the same result as autoDispose, without the state getting destroyed during navigation
Check out https://github.com/rrousselGit/river_pod/issues/1329#issuecomment-1086828636 That looks like a bug with StateProvider (& likely State/ChangeNotifierProvider)
@rrousselGit
I'm confused.
ref.keepAlive()
: Requests for the state of a provider to not be disposed when all the listeners of the provider are removed.ref.onCancel(ref.invalidateSelf)
: when the last listener of the provider is removed, invalidates the state of the provider.With this code, I expected ref.onCancel(ref.invalidateSelf)
will cause invalidates the state of the provider before go to screen 2 because ref.invalidateSelf
is called. If the expected behavior is keeping the state during navigation then no one will understand why, it's more like a bug to me if you implement that.
With this code, I expected ref.onCancel(ref.invalidateSelf) will cause invalidates the state of the provider before go to screen 2 because ref.invalidateSelf is called.
No it won't.
ref.read
does not triggers onCancel
onCancel triggers when the last listener is removed. ref.read
doesn't add/remove listeners, so it won' trigger onCancel,
The bug with StateProvider is because it's implemented as two providers (StateProvider vs StateProvider.notifier, where the former "watch" the later). So with autoDispose, a listener is added then removed immediately
But I think I'll make a breaking change and:
So doing:
Button(
onTap: () => ref.read(autoDisposeProvider.notifier).state++,
)
and then tapping the button will prevent the provider from getting disposed until the button is unmounted.
That's effectively what we've discussed before and what I originally wanted to do for the 1.0.0. This change is part of why context.read was changed to ref.read inside widgets – I just didn't go through with it.
Although I guess this won't cause the provider to be disposed when leaving screen2 and we'd have to leave screen 1. 🤔
I like this idea of the breaking change. I have definitely run into problems related to those apis.
Although I guess this won't cause the provider to be disposed when leaving screen2 and we'd have to leave screen 1. 🤔
@rrousselGit Let's consider it a bit.
I think people still want to read a provider everywhere without the need to keep it alive. If everything is "watch" then it's hard to auto dispose a provider because we need to care about every read in the whole app.
The scenario of this ticket can be solved easily by using ref.watch
on screen 1, it is just folks don't want rebuild when it's not necessary.
I think we should work on how to "read" an autoDispose provider and keep it alive until it's done.
@XuanTung95
I agree that making the read
API into a watch
API is counter intuitive.
The scenatio that was initially presented was that screen 1 doesn't care about the provider, and therefore doesn't need to watch it, it only initialized it with some value for screen 2.
If I have a normal provider or family which is not .autoDispose. Later I want to .autoDispose it, could I have a method dontKeepAlive? The workaround would be to create .autoDispose and set keepAlive then store the KeepAliveLink, then close() it, which is a lot of code.
I don't understand what you're trying to do. Could you share some code?
@rrousselGit
I edit my example. It looks like this.
final myProvider = ChangeNotifierProvider((ref) {
Future.delayed(Duration(seconds: 2)).then((value) {
ref.autoDispose(); // <- this provider become autoDispose from now on
});
Future.delayed(Duration(seconds: 5)).then((value) {
ref.keepAlive(); // <- back to normal
});
return ChangeNotifier();
});
Goal: Add 2 method .autoDispose()
and .keepAlive()
to Ref<State extends Object?>
.keepAlive()
: works same as normal .keepAlive()
of AutoDisposeRef. Maybe it return void instead of a Link object, because we can use .autoDispose()
to change the behavior..autoDispose()
: after call this method, provider work same as normal .autoDispose provider until .keepAlive() is called.I think this feature will complete the .keepAlive()
implementation you did. Allow folks to controller the life-cycle of provider more flexible.
It can solve the issue of this ticket by using .autoDispose()
method once screen B is disposed.
That's already doable:
final myProvider = ChangeNotifierProvider.autoDispose((ref) {
var link = ref.keepAlive();
Timer(Duration(seconds: 2), () {
link.cancel();
});
Timer(Duration(seconds: 5), () {
ref.keepAlive();
});
return ChangeNotifier();
});
I know, but it's only possible for .autoDispose, other providers also need to be disposed if nessesarry. The current implementation is not friendly if I don't want to use .autoDispose.
That's not a bug, that's a feature. Marking a provider as .autoDispose is critical for making hard-to-catch bugs a compilation error instead. It being necessary is voluntary and this will not be changed.
.autoDispose is critical for making hard-to-catch bugs a compilation error instead
Could you give an example bug? Because most bugs I got is from auto thing.
Listening an autoDispose provider in a not autoDispose provider:
final provider = Provider((ref) {
ref.watch(autoDisposeProvider);
});
This will cause the autoDispose provider to never be disposed once provider
is initialized, which is almost always undesired and very difficult to debug.
This is currently voluntarily made to be a compilation error, and is the reason why we do Provider.autoDispose(...)
instead of Provider(..., autoDispose: true)
This is currently voluntarily made to be a compilation error
I understand. But it has nothing to do with my proposal. The normal provider still cannot watch an autoDispose provider. It just add a away to dispose the normal provider.
Your proposal (ref.autoDispose()) introduces the same problem:
final a = Provider((ref) {
ref.autoDispose();
});
final b = Provider((ref) {
ref.watch(a); // cause a to never be disposed
});
This should be a compilation error, but isn't with your proposal.
Using .autoDispose
here is desired. By doing what I suggested here https://github.com/rrousselGit/river_pod/issues/1329#issuecomment-1088794571 then you would indeed get a compilation error if you tried to watch the provider in a non-autoDispose provider.
They are 2 different use cases. If I call ref.autoDispose(), I should expect it do not dispose if someone is listening. So how can it be a bug if I want it to behave that way.
final a = Provider((ref) {
ref.autoDispose();
});
final b = Provider((ref) {
ref.watch(a); // cause a to never be disposed => it's not true now because b can be disposed by using .autoDispose()
});
On a different note, what do you think about a simplification of ref.listen
+ close
onTap: () {
await ref.run(provider, (value) async {
// provider will stay alive until the callback completes
await Navigator.push(); // The await here will keep the provider alive until the route is poped
});
}
On a different note, what do you think about a simplification of
ref.listen
+close
onTap: () { await ref.run(provider, (value) async { // provider will stay alive until the callback completes await Navigator.push(); // The await here will keep the provider alive until the route is poped }); }
This make more sense, LGTM
On a different note, what do you think about a simplification of
ref.listen
+close
onTap: () { await ref.run(provider, (value) async { // provider will stay alive until the callback completes await Navigator.push(); // The await here will keep the provider alive until the route is poped }); }
I think we can do the same with this.
onTap: () async {
final value = ref.read(provider);
final link = value.keepAlive();
await Navigator.push();
link.close();
}
Adding run() method may be not necessary unless it is used a lot. And "go_router" package does not return Future when navigating, so it only work for Navigation 1.0.
You cannot call keepAlive
like you've described.
And you also need to handle exceptions, as if somehow code after the read
throws, the provider will never get disposed.
And "go_router" package does not return Future when navigating, so it only work for Navigation 1.0.
That's not Riverpod's fault though. You can make a feature request for it in go_router's repository
It has been a while, any update on this ticket?
I'm still skeptical about this. I think this would be a hazardous thing to implement and could cause quite a bit of confusion I'd prefer looking at a better solution. As once published, the harm done by such a feature could be enormous.
For this issue, I think a better alternative would be to give you the primitives to be able to implement this on your own.
I think the ref.onCancel
/ref.onResume
life-cycles are good candidates (combined with ref.keepAlive()
)
You should be able to use those to implement this on your own, without needing Riverpod to make this an official API.
Closing in favor of https://github.com/rrousselGit/riverpod/issues/1665. That issue would add the missing feature for implementing what's described here.
@rrousselGit Should we reopen this?
It was closed in favor of ref.onCancel
but ref.onCancel is not reliable to be used for this use-case as described at #2992 & #3759.
Nope. I have no plan to change this.
IMO this feature is too dangerous, as if the "first listener" is never added for some reason, the disposal will never happen.
We can raise new issues with regads to documenting how to init some values before pushing a route though.
Riverpod version
2.0.0-dev.4
adds adisposeDelay
feature to allautoDispose
providers and toProviderContainer
/ProviderScope
. This configures the amount of time before a provider is disposed when it is not listened.To avoid timing issues, it would be nice to add a "auto dispose after the provider is listened to once" mode, which make the provider auto disposable only after it is listened to once.
Example: