Skip to content

Commit

Permalink
feat!: add Capsule.map and remove ListenerHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
GregoryConrad committed Jul 26, 2023
1 parent a705b5d commit f5833f7
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 39 deletions.
27 changes: 13 additions & 14 deletions packages/flutter_rearch/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:rearch/rearch.dart';

void main() => runApp(const TodoApp());

/// Represents the [MimirIndex] that contains the movie dataset.
/// Represents the [MimirIndex] that contains the todos.
Future<MimirIndex> indexAsyncCapsule(CapsuleHandle use) async {
final instance = await Mimir.defaultInstance;
return instance.openIndex('todos', primaryKey: 'timestamp');
Expand All @@ -23,9 +23,9 @@ AsyncValue<MimirIndex> indexWarmUpCapsule(CapsuleHandle use) {

/// Acts as a proxy to the warmed-up [indexAsyncCapsule].
MimirIndex indexCapsule(CapsuleHandle use) {
return use(indexWarmUpCapsule).data.unwrapOrElse(
() => throw StateError('indexAsyncCapsule was not warmed up!'),
);
return use(indexWarmUpCapsule).dataOrElse(
() => throw StateError('indexAsyncCapsule was not warmed up!'),
);
}

/// Represents an item in the todos list.
Expand Down Expand Up @@ -454,16 +454,15 @@ class TodoItem extends RearchConsumer {
// The following uses a more advanced technique in rearch: inline capsules.
// This is similar to `select` in other state management frameworks, but
// inline capsules are much more powerful because they are full capsules.
// Please read the documentation to learn more about inline capsules
// *before using them*, because:
// > with great power comes great responsibility
// (you can accidentally cause leaks if you're not careful)
// Please read the documentation for more.
final (:title, :description, :timestamp, :completed) = use(
(CapsuleReader use) => use(todoListCapsule).dataOrElse(
() => throw StateError(
'In order to display a TodoItem, the todo list must have data!',
),
)[index],
todoListCapsule.map(
(asyncList) => asyncList.dataOrElse(
() => throw StateError(
'In order to display a TodoItem, the todo list must have data!',
),
)[index],
),
);

final (:updateTodo, :deleteTodo) = use(todoListManagerCapsule);
Expand Down Expand Up @@ -520,7 +519,7 @@ class DynamicBackground extends RearchConsumer {
// We need to use this more advanced side effect since we need to be able
// to grab the most up-to-date copy of the circles when we check to see
// if we actually need to add a new circle (if we used the regular state
// effect, the closure would capture an outdated copy).
// effect, the closure would capture an outdated copy of the state).
final (getCircles, setCircles) =
use.stateGetterSetter(<SplashCircleProperties>{});

Expand Down
45 changes: 24 additions & 21 deletions packages/rearch/lib/rearch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,21 @@ class CapsuleContainer implements Disposable {
) as _CapsuleManager<T>;
}

/// Reads the current data of the supplied [Capsule].
/// Reads the current data of the supplied [Capsule],
/// initializing it if needed.
T read<T>(Capsule<T> capsule) => _managerOf(capsule).data;

/// *Temporarily* listens to changes in a given set of [Capsule]s.
/// If you want to listen to capsule(s) *not temporarily*,
/// instead just make an impure capsule and [read] it once to initialize it.
/// `listen` calls the supplied listener immediately,
///
/// Calls the supplied [listener] immediately,
/// and then after any capsules its listening to change.
@UseResult('ListenerHandle will leak its listener if it is not disposed')
ListenerHandle listen(CapsuleListener listener) {
/// You *must* call the returned dispose [Function]
/// when you no longer need the listener in order to prevent leaks.
///
/// If you want to listen to capsule(s) *not temporarily*, instead just make
/// a non-idempotent capsule and [read] it once to initialize it.
@UseResult('Listener will leak in the container if it is not disposed')
void Function() listen(CapsuleListener listener) {
// Create a temporary *non-idempotent* capsule so that it doesn't get
// idempotent garbage collected
void capsule(CapsuleHandle use) {
Expand All @@ -93,9 +98,9 @@ class CapsuleContainer implements Disposable {
}

// Put the temporary capsule into the container so it gets data updates
read(capsule);
final manager = _managerOf(capsule);

return ListenerHandle._(this, capsule);
return manager.dispose;
}

/// Runs the supplied [callback] the next time [capsule] is
Expand All @@ -113,9 +118,8 @@ class CapsuleContainer implements Disposable {
// automatically disposed whenever the supplied capsule is updated/disposed
// via the idempotent gc.
void tempCapsule(CapsuleHandle use) => use<T>(capsule);
final manager = _CapsuleManager(this, tempCapsule);
final manager = _managerOf(tempCapsule);
manager.toDispose.add(callback);
_capsules[tempCapsule] = manager;

return () {
manager.toDispose.remove(callback);
Expand All @@ -132,15 +136,14 @@ class CapsuleContainer implements Disposable {
}
}

/// A handle onto the lifecycle of a listener from [CapsuleContainer.listen].
/// You *must* [dispose] the [ListenerHandle]
/// when you no longer need the listener in order to prevent leaks.
class ListenerHandle implements Disposable {
ListenerHandle._(this._container, this._capsule);

final CapsuleContainer _container;
final _UntypedCapsule _capsule;

@override
void dispose() => _container._capsules[_capsule]?.dispose();
/// Provides the [map] convenience method on [Capsule]s.
extension CapsuleMapper<T> on Capsule<T> {
/// Maps this [Capsule] (of type [T]) into
/// a new idempotent [Capsule] (of type [R])
/// by applying the given [mapper].
///
/// This is similar to `.select()` in some other libraries.
Capsule<R> map<R>(R Function(T) mapper) {
return (CapsuleReader use) => mapper(use(this));
}
}
8 changes: 4 additions & 4 deletions packages/rearch/test/basic_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,19 @@ void main() {
void listener(CapsuleReader use) => states.add(use(stateful).$1);

setState(1);
final handle1 = container.listen(listener);
final dispose1 = container.listen(listener);
setState(2);
setState(3);

handle1.dispose();
dispose1();
setState(4);

setState(5);
final handle2 = container.listen(listener);
final dispose2 = container.listen(listener);
setState(6);
setState(7);

handle2.dispose();
dispose2();
setState(8);

expect(states, equals([1, 2, 3, 5, 6, 7]));
Expand Down

0 comments on commit f5833f7

Please sign in to comment.