run_once

An extremely simple library for running 'things' exactly once. No matter how many times that 'thing' is called.

How does it work?

Well it's very simple. We basically use the StackTrace.current and carefully use this to keep record on when a function has been called. It means that the call stack is basically used as a unique identifier for when and where a function has been called.

Rationale

This library could be considered an anti-pattern in itself and a little exercise, but imagine a Flutter StatefulWidget with its build that you cannot rely on being called only when you yourself call setState. In this case you could use runOnce to run something once during this widget's lifetime no matter the times build was called.

Worth a mention; there also is the AsyncMemoizer.runOnce for async functions from package:async but that one isn't aware of the call stack and works internally in a much simpler manner.

Usage

Pure dart example;

import 'package:run_once/run_once.dart';

void main() async {
  /// This will print 'called 1' exactly 1 time although
  /// we call this function 10 times.
  for (var i = 0; i < 10; i++) {
    runOnce(() {
      print('called 1');
    });
  }

  /// This will print 'called 2' exactly 2 times as the [runOnce] simply
  /// isn't called in the same spot.
  runOnce(() {
    print('called 2');
  });
  runOnce(() {
    print('called 2');
  });

  /// This will print 'called 3' exactly 3 times although
  /// we call this function 15 times.
  /// Note the `forDuration` is exactly a [Duration] of 500 milliseconds.
  /// Using `forDuration` means for a duration of 500 milliseconds [runOnce]
  /// can only run once.
  /// So we loop 15 times in this loop and wait 100 milliseconds in each
  /// loop cycle; thus 'called 3' is printed exactly 3 times.
  for (var i = 0; i < 15; i++) {
    await Future.delayed(Duration(milliseconds: 100));

    runOnce(() {
      print('called 3');
    }, forDuration: Duration(milliseconds: 500));
  }
}

Flutter with a StatefulWidget example;

import 'package:run_once/run_once.dart';

// This example uses the state_notifier package
class _SomeWidgetState extends State<SomeWidget> {
  @override
  void dispose() {
    /// When this is called [runOnce] will be able to run once again
    /// for this class alone.
    runOnceDestroy();
    
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    final state = context.watch<MyState>();
    
    return state.when(
      data: () => Placeholder(),
      finished: () {
        runOnce(() {
          /// This is always only called once 
          /// until [dispose] is called.
          Navigator.pushNamed(context, '/b');
        });

        return Placeholder();
      },
    );
  }  
}

Disclaimer

This library isn't really tested very comprehensively at all. So it might not work in every use case.

Libraries

run_once
Support for doing something awesome.