Async Capsules
Often, you want to make a capsule that wraps around some asynchronous code so that asynchronous code can be utilized in other capsules. This poses an interesting problem, as every capsule is synchronous by nature. The solution: side effects! Side effects are indeed the mechanism that allows for external events to trigger capsule updates, which is exactly what we need here.
An Example
int countCapsule(CapsuleHandle _) => 0;
/// Our "raw" async capsule that directly returns a Future (can also return a Stream).
Future<int> delayedAsyncCapsule(CapsuleHandle use) async {
// All capsule reads and side effects *must* be before the first `await`.
// (But often, it is preferable to avoid side effects in async capsules.)
final count = use(countCapsule);
final delayedCount = await Future.delayed(
const Duration(seconds: 1),
() => count,
);
return delayedCount + 1;
}
/// Our "wrapper" capsule that returns an AsyncValue, which is often more useful in UI code.
/// This wrapper also ensures that delayedAsyncCapsule's value is cached within the Container,
/// as this capsule utilizes a side effect.
AsyncValue<int> delayedCapsule(CapsuleHandle use) {
final delayed = use(delayedAsyncCapsule);
return use.future(delayed);
}
// A macro like the following is *planned* to make this easier/less error-prone.
// If you would like to see this macro sooner rather than later, please +1 this issue:
// https://github.com/dart-lang/language/issues/3210
@asyncCapsule
Future<int> delayed(
@countCapsule int count,
) async {
final delayedCount = await Future.delayed(
const Duration(seconds: 1),
() => count,
);
return delayedCount + 1;
}
Refreshable Asynchronous Capsules
The created delayedCapsule
in the above example is just a read-only "view" of the future.
It doesn't contain any state of its own and merely stores the current value of the async capsule.
If you want to refresh the delayed capule's state, say by fetching new data from online,
we need a slightly different approach which will swap in a new future.
refreshableFuture
side effect to the rescue!
(AsyncValue<int>, void Function()) refreshableDelayedCapsule(CapsuleHandle use) {
return use.refreshableFuture(() {
return Future.delayed(const Duration(seconds: 1), () => 1234);
});
}
There is also an equivalent for just invalidation: invalidatableFuture
.
The difference is that invalidatableFuture
only executes a new future when it is currently in use,
whereas refreshableFuture
will always execute a new future when refreshed.
What is AsyncValue
/AsyncState
?
You may notice that when applying the asynchronous side effects,
you are given data of type AsyncValue<T>
/AsyncState<T>
.
This is entirely intentional;
it allows you to handle the current state of asynchronous code synchronously,
which is needed as ReArch builds are completely synchronous and (in Dart) cannot throw.
As such, it is highly recommended that you learn how to properly work with AsyncValue
/AsyncState
instead of trying to avoid it.