safe_effect

safe_effect is a lightweight Flutter utility for safely executing side-effects (API calls, payments, analytics, sync logic) without accidentally running them multiple times due to widget rebuilds or app lifecycle changes.

Flutter rebuilds widgets aggressively and provides no built-in guarantees around how often imperative logic is executed. As a result, side-effects are frequently triggered more than intended.

This package introduces explicit, lifecycle-aware guards so that critical side-effects run only when intended and only as often as intended.


Features

  • Execute side-effects only once per defined scope
  • Prevent duplicate execution caused by:
    • widget rebuilds
    • navigation
    • app background → foreground transitions
  • Lifecycle-aware execution on app resume
  • Configurable execution scopes:
    • screen
    • session
    • app (persistent)
  • Optional persistence using SharedPreferences
  • Minimal API with no dependency on state-management solutions

Getting started

Prerequisites

  • Flutter SDK
  • Dart 3.0 or later

Installation

Add the dependency to your pubspec.yaml:

dependencies:
  safe_effect: ^0.0.1


## Usage

### Run a side-effect only once

This is the most common use case.
Even if the widget rebuilds multiple times, the effect will execute only once
for the selected scope.

```dart
SafeEffect.runOnce(
  key: 'fetch_profile',
  scope: EventScope.session,
  effect: fetchUserProfile,
);

Conditional execution

You can prevent execution unless a condition is satisfied.

SafeEffect.runOnce(
  key: 'payment_init',
  scope: EventScope.session,
  condition: () => cart.totalAmount > 0,
  effect: initiatePayment,
);

Execution scopes

safe_effect supports different lifetimes for guarded execution:

  • EventScope.screen Resets when the widget tree is disposed.

  • EventScope.session Resets when the app process is killed.

  • EventScope.app Persists across app restarts (requires persistent store).

Example:

SafeEffect.runOnce(
  key: 'onboarding_shown',
  scope: EventScope.app,
  effect: showOnboarding,
);

Execute logic on app resume

Useful for syncing data or refreshing state when the user returns to the app.

SafeEffect.onResume(
  key: 'sync_subscription',
  debounce: Duration(seconds: 10),
  effect: syncSubscriptionStatus,
);

The optional debounce prevents rapid repeated executions when the app is resumed frequently.


Enable persistent guarding (optional)

To persist guarded execution across app restarts, configure a persistent store:

final prefs = await SharedPreferences.getInstance();

SafeEffect.configurePersistentStore(
  PersistentGuardStore(prefs),
);

Reset a guarded effect

You may explicitly allow a guarded effect to run again:

await SafeEffect.reset(
  'fetch_profile',
  EventScope.session,
);

For a complete runnable example, see the /example directory.


Additional information

When should I use this package?

Use safe_effect when:

  • an API call must not be triggered multiple times
  • a payment or checkout flow must be protected
  • analytics events should not be duplicated
  • resume-based sync logic should be controlled

When should I NOT use this package?

This package is intentionally not designed for:

  • retrying failed network requests
  • background execution
  • task queues or job schedulers
  • state management

Contributing

Contributions are welcome. Please open an issue before submitting large changes so behavior and scope can be discussed.

Issues and support

  • Bugs and feature requests can be filed via GitHub issues

Libraries

safe_effect