cached_value 0.2.0
cached_value: ^0.2.0 copied to clipboard

A simple way to cache values that result from rather expensive operations.

cached_value #

Pub

A simple way to cache values that result from rather expensive operations.

It is useful to cache values that:

  • Are computed from other values in a consistent way;
  • Can be changed given known and unknown conditions;
  • Should not be computed on every access (like a getter);

Installation #

Add to pubspec.yaml:

    dependencies:
       cached_value: <most recent version>

Find the most recent version on pub.

Usage #

A cache can be created from a simple manually controlled cache and composed with automatic functionalities. Such as dependencies and time to live.

Creating a cache: #

A simple cache is only invalidated manually.

  int factorial(int n) {
    if (n < 0) throw ('Negative numbers are not allowed.');
    return n <= 1 ? 1 : n * factorial(n - 1);
  }

  int originalValue = 1;
  final factorialCache = CachedValue(() => factorial(originalValue));
  print(factorialCache.value); // 1

  originalValue = 6;

  print(factorialCache.value); // 1
  print(factorialCache.isValid) // true, invalid only when invalidate is called
  
  // mark as invalid
  factorialCache.invalidate();
  print(factorialCache.isValid); // false

  print(factorialCache.value); // 720
  print(factorialCache.isValid); // true

Accessing value when the cache is invalid refreshes the cache. It can be refreshed manually via the refresh method:

  // ...
  originalValue = 12;
  factorialCache.refresh();

  print(factorialCache.value); // 12

Composing a cache #

A cache can be composed with more resources via a declarative API. By doing that, it is possible to add TTL and dependency without diverging from the original behavior of a cache.

Example:

  int factorial(int n) {
    if (n < 0) throw ('Negative numbers are not allowed.');
    return n <= 1 ? 1 : n * factorial(n - 1);
  }
  
  int originalValue = 1;
  final fancyFactorialCache = CachedValue(
      () => factorial(originalValue),
  ).withDependency(() => originalValue) // Add dependency
  .withTimeToLive(lifetime: Duration(seconds: 4)); // Add TTL

You can even create your behavior yourself by extending SingleChildCachedValue.

Adding dependency #

A dependent cache is marked as invalid if its dependency value has changed.

  int factorial(int n) {
    if (n < 0) throw ('Negative numbers are not allowed.');
    return n <= 1 ? 1 : n * factorial(n - 1);
  }
  
  int originalValue = 1;
  final factorialCache = CachedValue(
      () => factorial(originalValue),
  ).withDependency(() => originalValue);
  print(factorialCache.value); // 1
  print(factorialCache.isValid); // true
  
  // update value
  originalValue = 6;
  print(factorialCache.isValid); // false

  print(factorialCache.value); // 720
  print(factorialCache.isValid); // true

⚠️Important: The dependency callback is called on every value access. So it is recommended to keep it as declarative as possible.

// Avoid this:
final someCache = CachedValue(
  // ...
).withDependency(() => someExpensiveOperation(originalValue));

Adding time to live #

A cache can be automatically marked as invalid some time after a refresh.

    int factorial(int n) {
        if (n < 0) throw ('Negative numbers are not allowed.');
        return n <= 1 ? 1 : n * factorial(n - 1);
    }
  
    int originalValue = 1;
    final factorialCache = CachedValue(
      () => factorial(originalValue),
    ).withTimeToLive(
      lifetime: Duration(seconds: 3),
    );
    
    originalValue = 6;
    
    print(factorialCache.value); // 1
    
    await Future.delayed(Duration(seconds: 3));
    
    print(factorialCache.value); // 720

More docs #

There is more detailed docs on the API documentation.

Motivation #

Some imperative APIs such as the canvas paint on Flutter render objects of Flame's components may benefit from values that can be stored and reused between more than a single frame (or paint).

In some very specific cases, I found very convenient to store some objects across frames, like Paint and TextPainter instances.

Example on a render object:

class BlurredRenderObject extends RenderObject {

  // ...

  double _blurSigma = 0.0;
  double get blurSigma => _blurSigma;
  set blurSigma(double value) {
    _blurSigma = blurSigma;
    markNeedsPaint();
  }

  // ...

  @override
  void paint(PaintingContext context, Offset offset) {
    final canvas = context.canvas;


    final paint = Paint()..maskFilter = MaskFilter.blur(
        BlurStyle.normal, blurSigma
    );
    canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);
  }
}

Can be changed to:

class BlurredRenderObject extends RenderObject {

  // ...

  double _blurSigma = 0.0;
  double get blurSigma => _blurSigma;
  set blurSigma(double value) {
    _blurSigma = blurSigma;
    markNeedsPaint();
  }

  // Add cache:
  late final paintCache = CachedValue(
     () => Paint()..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma),
  ).withDependency(() => blurSigma);

  // ...

  @override
  void paint(PaintingContext context, Offset offset) {
    final canvas = context.canvas;

    // use cache:
    final paint = paintCache.value;
    canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);
  }
}
6
likes
130
pub points
22%
popularity

Publisher

blue-fire.xyz

A simple way to cache values that result from rather expensive operations.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

collection, meta

More

Packages that depend on cached_value