riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

Implement cacheTime/staleTime

Open rrousselGit opened this issue 1 year ago • 15 comments

It has been decided to remove cacheTime as part of the initial release, as the initial implementation had some issues.
This issue is for tracking the re-introduction of cacheTime, accompagned by staleTime.

staleTime would be the amount of time before the value is considered obsolete. At this point, the provider should re-execute when given the opportunity. But the value exposed wouldn't be destroyed immediately. Instead the value will be available for caheTime after stateTime as expired.

rrousselGit avatar Sep 21 '22 19:09 rrousselGit

For reference, even if these APIs won't be part of the initial 2.0 release:

  • they will be added in the future
  • you can implement cacheTime yourself using ref.keepAlive() + a Timer()

rrousselGit avatar Sep 21 '22 21:09 rrousselGit

TL;DR about implementing cacheTime on your own:

extension on AutoDisposeRef {
  // When invoked keeps your provider alive for [duration]
  void cacheFor(Duration duration) {
    final link = keepAlive();
    final timer = Timer(duration, () => link.close());
    ref.onDispose(() => timer.cancel());
  }
}

final myProvider = Provider.autoDispose<int>((ref) {
  ref.cacheFor(Duration(minutes: 5));
});

rrousselGit avatar Sep 25 '22 11:09 rrousselGit

@rrousselGit I have two questions about this implementation.

Question 1. By looking at cacheTime and disposeDelay, I read, respectively:

The minimum amount of time before an autoDispose provider can be disposed if not listened.

and

The amount of time before a provider is disposed after its last listener is removed.

I feel like the implementation above is closer to disposeDelay than it is to cacheTime (which is actually what I need in my use case, so that's cool). Am I getting this right?

Question 2. If keepAlive works the same as maintainState, how will the dispose event ever trigger - we are telling this provider not to dispose, right? This would mean that the callback inside onDispose will never trigger, right?

lucavenir avatar Oct 01 '22 09:10 lucavenir

I feel like the implementation above is closer to disposeDelay than it is to cacheTime (which is actually what I need in my use case, so that's cool). Am I getting this right?

No 😋

What I implemented is indeed cacheTime. When it was available in the dev release, that's quite litteraly how it worked.

The onDispose is for when using ref.watch/invalidate/... A provider can get disposed even if it's listened when using those.

rrousselGit avatar Oct 01 '22 10:10 rrousselGit

Re-reading the definitions and the implementation I totally see that it's cacheTime being implemented there. Thank you @rrousselGit.

For reference for other users, I tried to implement disposeDelay, too! This seems to be working, but I'd like to have your "blessings" 😛 Let me know.

extension TestExtension<T> on AutoDisposeRef<T> {
  void disposeDelay(Duration duration) {
    final link = keepAlive();
    Timer? timer;

    onCancel(() {
      timer?.cancel();
      timer = Timer(duration, link.close);
    });

    onDispose(() {
      timer?.cancel();
    });

    onResume(() {
      timer?.cancel();
    });
  }

  void cacheFor(Duration duration) {
    final link = keepAlive();
    final timer = Timer(duration, link.close);

    onDispose(timer.cancel);
  }
}

EDIT. implemented remi's suggestions.

lucavenir avatar Oct 02 '22 10:10 lucavenir

You'll want to restart the timer too on onResume.

But let's be clear: disposeDelay isn't going to be added back

rrousselGit avatar Oct 02 '22 10:10 rrousselGit

You'll want to restart the timer too on onResume.

True. Thanks!

But let's be clear: disposeDelay isn't going to be added back

Why is that?

lucavenir avatar Oct 02 '22 12:10 lucavenir

I think extra functionalities shouldn't be included in the base package. All we need is flexibility and stability.

We wanted cacheTime and disposeDelay because before that, working with combination of Timer and ref.maintainState was buggy. Now I see KeepAliveLink works flawlessly. It's just few lines of code as implementation and again, 1 line of code in each provider.

esenmx avatar Oct 02 '22 13:10 esenmx

@rrousselGit sorry to bother you, but would the following be a fair implementation of staleTime?

void staleTime(Duration duration) {
    var timer = Timer(duration, invalidateSelf);
    
    onAddListener((){
      timer.cancel();
      timer = Timer(duration, invalidateSelf);
    });
    
    onDispose((){
      timer.cancel();
    });
  }

lucavenir avatar Oct 05 '22 06:10 lucavenir

Possibly

Although both cacheTime and staleTime would be part of Riverpod itself, to simplify combining them with error handling and async (cacheTime/staleTime should start when a FutureProvider completes and even after an error)

Also, cacheTime must start after staleTime. So there's a bit of logic needed here

rrousselGit avatar Oct 05 '22 07:10 rrousselGit