Skip to content

Commit

Permalink
feat: add invalidatableFuture side effect (#19)
Browse files Browse the repository at this point in the history
Fixes #18
  • Loading branch information
GregoryConrad committed Dec 16, 2023
1 parent c0e571e commit 7ae2d06
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 5 deletions.
52 changes: 52 additions & 0 deletions packages/rearch/lib/src/side_effects.dart
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ extension BuiltinSideEffects on SideEffectRegistrar {
/// You supply a [futureFactory], which is a function that must
/// return a new instance of a future to be watched.
///
/// See also [invalidatableFuture], which enables lazy future invalidation.
///
/// Internally creates the future to watch on the first build
/// and then again whenever the returned callback is invoked.
(AsyncValue<T>, void Function()) refreshableFuture<T>(
Expand All @@ -302,6 +304,56 @@ extension BuiltinSideEffects on SideEffectRegistrar {
final futureState = use.future(currFuture);
return (futureState, () => setFuture(futureFactory()));
}

/// A side effect that allows you to watch a future lazily
/// (by invoking the first callback)
/// that can be invalidated lazily (by invoking the second callback).
///
/// You supply a [futureFactory], which is a function that must
/// return a new instance of a future to be watched.
///
/// See also [refreshableFuture], which eagerly refreshes futures.
///
/// Note: this returns an `AsyncValue<T> Function()` because returning a
/// function enables this side effect to determine whether or not there is any
/// demand for the future itself, enabling it to be evaluated lazily.
(AsyncValue<T> Function(), void Function()) invalidatableFuture<T>(
Future<T> Function() futureFactory,
) {
final rebuild = use.rebuilder();
final (getAsyncState, setAsyncState) =
use.rawValueWrapper<AsyncValue<T>>(() => AsyncLoading<T>(None<T>()));
final (getFutureCancel, setFutureCancel) =
use.rawValueWrapper<void Function()?>(() => null);
use.register((api) => api.registerDispose(() => getFutureCancel()?.call()));

return (
() {
if (getFutureCancel() == null) {
setAsyncState(AsyncLoading<T>(getAsyncState().data));
final subscription = futureFactory().asStream().listen(
(data) {
setAsyncState(AsyncData(data));
rebuild();
},
onError: (Object error, StackTrace trace) {
setAsyncState(AsyncError(error, trace, getAsyncState().data));
rebuild();
},
);
setFutureCancel(subscription.cancel);
}

return getAsyncState();
},
() {
getFutureCancel()?.call();
setFutureCancel(null);

rebuild();
},
);
}
}

/// Checks to see whether [newDeps] has changed from [oldDeps]
Expand Down
6 changes: 1 addition & 5 deletions packages/rearch/test/basic_test.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import 'package:rearch/rearch.dart';
import 'package:test/test.dart';

CapsuleContainer useContainer() {
final container = CapsuleContainer();
addTearDown(container.dispose);
return container;
}
import 'util.dart';

void main() {
test('basic count example', () {
Expand Down
34 changes: 34 additions & 0 deletions packages/rearch/test/side_effects.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:rearch/rearch.dart';
import 'package:test/test.dart';

import 'util.dart';

void main() {
test('invalidatableFuture', () async {
var shouldError = false;

(AsyncValue<int> Function(), void Function()) testCapsule(
CapsuleHandle use,
) {
return use.invalidatableFuture(() async {
if (shouldError) throw Exception();
return 0;
});
}

final container = useContainer();
final (getState, invalidate) = container.read(testCapsule);

expect(getState(), equals(const AsyncLoading(None<int>())));
await Future.delayed(Duration.zero, () => null);
expect(getState(), equals(const AsyncData(0)));

shouldError = true;
invalidate();

expect(getState(), equals(const AsyncLoading(Some(0))));
await Future.delayed(Duration.zero, () => null);
expect(getState(), isA<AsyncError<int>>);
expect(getState().data, equals(const Some(0)));
});
}
8 changes: 8 additions & 0 deletions packages/rearch/test/util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:rearch/rearch.dart';
import 'package:test/test.dart';

CapsuleContainer useContainer() {
final container = CapsuleContainer();
addTearDown(container.dispose);
return container;
}

0 comments on commit 7ae2d06

Please sign in to comment.