Dart Language
Warning: these notes aren't about Flutter
Intro
TODO
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 on it, 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.
Here 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();
}
Here is a 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 Duration
s 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 here.
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());
}
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);
}