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.