Dart Language
Warning: these notes aren't about Flutter
Intro
My notes about Dart Lang.
Pattern Matching
Introduced in Dart 3. TODO
Records
Introduced in Dart 3. TODO
Nice Utilities
Debounce
A class that prevents a method from being excessively called multiple times in a short period.
import 'dart:async';
class Debouncer {
final int milliseconds;
Timer? _timer;
final Duration _delay;
Debouncer({required this.milliseconds})
: _delay = Duration(milliseconds: milliseconds);
void run(void Function() f) {
_timer?.cancel();
_timer = Timer(_delay, f);
}
void dispose() {
_timer?.cancel();
_timer = null;
}
}
Usage:
final debouncer = Debouncer(milliseconds: 250);
int main() {
debouncer.run(
() {
print("Won't be executed because of the next line");
}
);
debouncer.run(
() {
print("Will be executed after 250 milliseconds");
}
);
debouncer.dispose();
}
The function f passed to the method run is executed after a pre-defined delay. When the run method is called again in the middle of a delay, the previous call to f is cancelled in favor to the current f and the delay starts again from 0.
As this implementation uses a Timer object, it’s very important to dispose the debouncer when it won’t be needed anymore.
Singleton Pattern
It’s a class that can have only one object instantiated. Every time the new operator is called, the constructor returns the same instance.
class Singleton {
String name = 'name';
/// Internal instance.
/// Returned every time the command `new Singleton()` is called
static Singleton? _instance;
/// Private constructor.
/// Used to run internal logic at construction time
Singleton._privateConstructor() {
// add logic here
}
/// Public constructor.
/// Returns the same instance every time a new object would be created.
factory Singleton() {
_instance ??= Singleton._privateConstructor();
return _instance!;
}
}
void main() {
final objA = Singleton();
final objB = Singleton();
print(objA == objB); // true
print(objA.name == objB.name); // true
objA.name = 'nome';
print(objA.name == objB.name); // true
}
In the above code, the attribute name was added only for testing purposes. The private constructor is needed because it’s the true responsible to initialize the object’s attributes, allocate it in memory and return its reference. The factory method doesn’t actually create anything, it’s just used to call the private constructor or the previously created instance.
In this post, there is another implementation that creates an instance right at the class declaration:
class Singleton {
static final Singleton _instance = Singleton._privateConstructor();
factory Singleton() {
return _instance;
}
Singleton._privateConstructor();
}
In this post, there is an implementation that exposes the instance attribute:
class Singleton {
Singleton._privateConstructor();
static final Singleton instance = Singleton._privateConstructor();
}
In the code above, instance can be exposed because it’s final, so once attributed, can’t be changed.
Observer Pattern
Observer class:
abstract class Observer {
void onUpdate(String data);
}
class ConcreteObserver implements Observer {
final String name;
ConcreteObserver(this.name);
@override
void onUpdate(String data) {
print('$name received data: $data');
}
}
Subject class:
class Subject {
List<Observer> _observers = [];
void addObserver(Observer observer) {
_observers.add(observer);
}
void removeObserver(Observer observer) {
_observers.remove(observer);
}
// Method to send data to observers
void sendData(String data) {
notifyObservers(data);
}
void notifyObservers(String data) {
for (final obs in _observers) {
obs.onUpdate(data);
}
}
}
Example of usage:
void main() {
// Create a subject
final subject = Subject();
// Create observers
final observer1 = ConcreteObserver("obs1");
final observer2 = ConcreteObserver("obs2");
// Register observers with the subject
subject.addObserver(observer1);
subject.addObserver(observer2);
// Send data to observers
subject.sendData('Hello');
// Remove an observer
subject.removeObserver(observer2);
// Send more data to observers
subject.sendData('World');
}
Example of output:
obs1 received data: Hello
obs2 received data: Hello
obs1 received data: World
Pair Class
If you want to implement by yourself:
class Pair<L, R> {
final L left;
final R right;
Pair(this.left, this.right);
}
void main() {
const pair = Pair<String, int>('key', 0);
print(pair.left);
print(pair.right);
}
Or you could just use the MapEntry class, which holds a key-value pair. To refer these getters as the typical left-right nomenclature, write an extension on MapEntry<K, V>.
extension MapEntryExt<K,V> on MapEntry<K, V> {
K get left => key;
V get right => value;
}
void main() {
const pair = MapEntry('key', 0);
print(pair.left);
print(pair.right);
}
DurationExtension
If you want to declare Durations in a less verbose way:
extension DurationExt on int {
Duration get ms => Duration(milliseconds: this);
Duration get sec => Duration(seconds: this);
Duration get min => Duration(minutes: this);
}
Usage:
final halfSec = 500.ms;
final twoSecs = 2.sec;
final oneHour = 60.min;
HexColor Extension
Extracted from StackOverflow.
extension HexColor on Color {
/// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#".
static Color fromHex(String hexString) {
final buffer = StringBuffer();
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
buffer.write(hexString.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
}
/// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`).
String toHex({bool leadingHashSign = true}) => '${leadingHashSign ? '#' : ''}'
'${alpha.toRadixString(16).padLeft(2, '0')}'
'${red.toRadixString(16).padLeft(2, '0')}'
'${green.toRadixString(16).padLeft(2, '0')}'
'${blue.toRadixString(16).padLeft(2, '0')}';
}
Usage:
void main() {
final Color color = HexColor.fromHex('#aabbcc');
print(color.toHex());
print(const Color(0xffaabbcc).toHex());
}
SizedBox Extension
If you want to add white spaces in a less verbose way.
extension SizedBoxExtension on num {
/// Creates the SizedBox of given height
SizedBox get sh => SizedBox(height: toDouble());
/// Creates the SizedBox of given width
SizedBox get sw => SizedBox(width: toDouble());
}
Basic usage:
Column(
children: [
32.sh,
Widget(),
Row(
children: [
Widget(),
16.sw,
Widget()
]
),
56.sh,
]
)
Size Extension
extension SizeExtension on BuildContext {
double get width => MediaQuery.of(this).size.width;
double get height => MediaQuery.of(this).size.height;
}
Basic Usage:
Container(
width: context.width,
height: context.height,
child: Widget(),
)
Random Numbers
Basic usage:
import 'dart:math';
void main() {
var random = Random();
random.nextBool();
random.nextDouble();
random.nextInt(100);
random.toString();
}
To generate numbers inside the interval , use the function:
int random(int min, int max) {
return min + Random().nextInt(max - min);
}