Skip to content

Auto-Dispose

The AutoDisposeManager handles automatic cleanup of reacton values when no widgets are watching them. This prevents memory leaks from abandoned state while supporting temporary navigation patterns through a grace period.

How It Works

When the last widget watching a reacton unmounts, the AutoDisposeManager starts a grace period timer. If no new widget starts watching the same reacton before the timer expires, the reacton is removed from the store.

If a new watcher appears during the grace period (e.g., the user navigates back to a previous screen), the timer is cancelled and the reacton's value is preserved.

Widget A watches reacton X
Widget A unmounts
  -> Grace period starts (5 seconds by default)

Case 1: Timer expires
  -> reacton X is removed from the store

Case 2: Widget B watches reacton X before timer expires
  -> Timer is cancelled, reacton X is preserved

AutoDisposeManager

Constructor

dart
AutoDisposeManager(
  ReactonStore store, {
  Duration gracePeriod = const Duration(seconds: 5),
})
ParameterTypeDefaultDescription
storeReactonStore(required)The store to manage
gracePeriodDuration5 secondsHow long to wait before disposing an unwatched reacton

API

MethodSignatureDescription
onWatchvoid onWatch(ReactonRef ref)Notify that a watcher started. Increments the watcher count and cancels any pending disposal timer.
onUnwatchvoid onUnwatch(ReactonRef ref)Notify that a watcher stopped. Decrements the watcher count and starts a disposal timer if count reaches zero.
watcherCountint watcherCount(ReactonRef ref)Get the current number of active watchers for a reacton.
cancelAllvoid cancelAll()Cancel all pending disposal timers without removing any reactons.
disposevoid dispose()Cancel all timers and clear all tracking state.

Usage

dart
final store = ReactonStore();
final autoDispose = AutoDisposeManager(store, gracePeriod: const Duration(seconds: 10));

// When a widget starts watching
autoDispose.onWatch(counterReacton.ref);

// When a widget stops watching
autoDispose.onUnwatch(counterReacton.ref);

// Check watcher count
print(autoDispose.watcherCount(counterReacton.ref)); // 0

// Clean up
autoDispose.dispose();

Opting Out with keepAlive

If a reacton should never be auto-disposed (e.g., global app state like authentication), set keepAlive: true in its options:

dart
final authTokenReacton = reacton<String?>(
  null,
  name: 'authToken',
  options: const ReactonOptions(keepAlive: true),
);

Reactons with keepAlive: true remain in the store regardless of watcher count. The AutoDisposeManager checks the keepAlive flag before scheduling disposal.

Grace Period

The grace period prevents premature disposal during navigation transitions. For example:

  1. User is on Screen A, which watches userReacton
  2. User navigates to Screen B (Screen A is unmounted)
  3. Grace period starts for userReacton
  4. User navigates back to Screen A within 5 seconds
  5. Screen A calls context.watch(userReacton) -- grace period is cancelled, value is preserved

Without a grace period, the user would see a loading state every time they navigate back, because the reacton would have been removed and re-initialized.

Configuring the Grace Period

Set a shorter or longer grace period based on your app's navigation patterns:

dart
// Shorter grace period for data that's cheap to re-fetch
final autoDispose = AutoDisposeManager(
  store,
  gracePeriod: const Duration(seconds: 2),
);

// Longer grace period for expensive computations
final autoDispose = AutoDisposeManager(
  store,
  gracePeriod: const Duration(seconds: 30),
);

Lifecycle Integration

The auto-dispose system integrates with Flutter's widget lifecycle through the subscription tracking mechanism:

  1. context.watch() calls store.subscribe(), which increments the subscriber count on the GraphNode
  2. When the Element is unmounted, the _ReactonSubscriptionTracker disposes all subscriptions
  3. Each disposed subscription decrements the subscriber count via node.removeSubscriber()
  4. When the count reaches zero, the auto-dispose timer starts (if configured)

This means auto-dispose works automatically with context.watch(), ReactonBuilder, ReactonConsumer, and ReactonSelector -- no extra code is needed in your widgets.

Example: Scoped Auto-Dispose

dart
class FeatureScope extends StatefulWidget {
  final Widget child;
  const FeatureScope({super.key, required this.child});

  @override
  State<FeatureScope> createState() => _FeatureScopeState();
}

class _FeatureScopeState extends State<FeatureScope> {
  late final AutoDisposeManager _autoDispose;

  @override
  void initState() {
    super.initState();
    final store = ReactonScope.read(context);
    _autoDispose = AutoDisposeManager(
      store,
      gracePeriod: const Duration(seconds: 10),
    );
  }

  @override
  void dispose() {
    _autoDispose.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => widget.child;
}

What's Next

Released under the MIT License.