Dart Extensions
Adding methods/properties to existing classes
Extensions add methods, properties, and operators to existing classes without modifying source or inheriting. You can attach utilities like capitalize to String, isPrime to int, distinct to List directly on types for better readability. Generic extensions are also supported, making them very useful for widget extensions in Flutter.
Extension κΈ°λ³Έ λ¬Έλ²
Extensionμ κΈ°μ‘΄ ν΄λμ€μ μμ€ μ½λλ₯Ό μμ νκ±°λ μμνμ§ μκ³ λ μλ‘μ΄ λ©μλ, getter, setter, μ°μ°μλ₯Ό μΆκ°ν μ μλ κΈ°λ₯μ λλ€.
extension <ExtensionName> on <TargetType> {
// methods, getters, setters, operators
}
String νμ₯ μμ
첫 κΈμ λλ¬Έμ λ³ν, μ΄λ©μΌ κ²μ¦, λ°λ³΅, νμ΄ν μΌμ΄μ€ λ± λ¬Έμμ΄ μ νΈλ¦¬ν°λ₯Ό μΆκ°ν©λλ€.
extension StringExtension on String {
String get capitalize =>
isNotEmpty ? '${this[0].toUpperCase()}${substring(1)}' : '';
bool get isValidEmail =>
RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);
String repeat(int n) => List.filled(n, this).join();
String toTitleCase() {
return split(' ')
.map((word) => word.isNotEmpty
? '${word[0].toUpperCase()}${word.substring(1)}'
: '')
.join(' ');
}
}
// μ¬μ© μμ
String name = 'john doe';
print(name.capitalize); // John doe
print(name.toTitleCase()); // John Doe
print('hello'.repeat(3)); // hellohellohello
print('test@example.com'.isValidEmail); // true
int νμ₯ μμ
μ΄ λ¨μλ₯Ό μ:λΆ:μ΄λ‘ λ³ν, μμ νλ³, ν©ν λ¦¬μΌ κ³μ° λ± μ μμ μν μ°μ°μ μΆκ°ν©λλ€.
extension IntExtension on int {
String toTimeString() {
int h = this ~/ 3600;
int m = (this % 3600) ~/ 60;
int s = this % 60;
return '${h.toString().padLeft(2, '0')}:'
'${m.toString().padLeft(2, '0')}:'
'${s.toString().padLeft(2, '0')}';
}
bool get isPrime {
if (this <= 1) return false;
if (this <= 3) return true;
if (this % 2 == 0 || this % 3 == 0) return false;
int i = 5;
while (i * i <= this) {
if (this % i == 0 || this % (i + 2) == 0) return false;
i += 6;
}
return true;
}
int get factorial {
if (this < 0) throw ArgumentError('μμμ ν©ν 리μΌμ μ μλμ§ μμ΅λλ€.');
if (this <= 1) return 1;
return this * (this - 1).factorial;
}
}
// μ¬μ© μμ
print(3665.toTimeString()); // 01:01:05
print(7.isPrime); // true
print(5.factorial); // 120
List<T> μ λ€λ¦ νμ₯ μμ
μ λ€λ¦ νμ νλΌλ―Έν°λ₯Ό μ¬μ©νμ¬ λͺ¨λ 리μ€νΈ νμ μ μ μ© κ°λ₯ν μ νΈλ¦¬ν°λ₯Ό λ§λλλ€.
extension ListExtension<T> on List<T> {
T? get firstOrNull => isEmpty ? null : first;
T? get lastOrNull => isEmpty ? null : last;
List<T> get distinct => toSet().toList();
List<List<T>> chunk(int size) {
return List.generate(
(length / size).ceil(),
(i) => sublist(
i * size,
(i + 1) * size > length ? length : (i + 1) * size,
),
);
}
}
// μ¬μ© μμ
List<int> numbers = [1, 2, 3, 4, 5, 1, 2];
print(numbers.distinct); // [1, 2, 3, 4, 5]
List<String> fruits = ['μ¬κ³Ό', 'λ°λλ', 'μ€λ μ§', 'λΈκΈ°', 'ν¬λ'];
print(fruits.chunk(2)); // [[μ¬κ³Ό, λ°λλ], [μ€λ μ§, λΈκΈ°], [ν¬λ]]
List<int> empty = [];
print(empty.firstOrNull); // null
Getter/Setter — νμ νμ± νμ₯
Stringμ μμ ν νμ λ³ν getterλ₯Ό μΆκ°νμ¬ int, double, boolλ‘ νμ±ν©λλ€.
extension NumberParsing on String {
int? get asIntOrNull => int.tryParse(this);
double? get asDoubleOrNull => double.tryParse(this);
bool get asBool {
final lower = toLowerCase();
return lower == 'true' || lower == '1'
|| lower == 'yes' || lower == 'y';
}
}
print('42'.asIntOrNull); // 42
print('3.14'.asDoubleOrNull); // 3.14
print('abc'.asIntOrNull); // null
print('YES'.asBool); // true
μ μ λ©€λ² (Static Members)
Extensionμ static λ©μλλ₯Ό μ μν μ μμ΅λλ€. λ¨, νΈμΆ μ Extension μ΄λ¦μ λͺ μν΄μΌ ν©λλ€.
extension DateTimeExtension on DateTime {
String get formattedDate =>
'$year-${month.toString().padLeft(2, '0')}-'
'${day.toString().padLeft(2, '0')}';
static DateTime fromFormattedString(String s) {
final parts = s.split('-');
if (parts.length != 3) {
throw FormatException('μλͺ»λ λ μ§ νμ: $s');
}
return DateTime(
int.parse(parts[0]),
int.parse(parts[1]),
int.parse(parts[2]),
);
}
static DateTime get tomorrow =>
DateTime.now().add(Duration(days: 1));
}
// μΈμ€ν΄μ€ λ©μλλ μ§μ νΈμΆ
print(DateTime.now().formattedDate); // 2023-11-15
// static λ©μλλ Extension μ΄λ¦μΌλ‘ νΈμΆ
final date = DateTimeExtension.fromFormattedString('2023-11-15');
print(DateTimeExtension.tomorrow);
μ λ€λ¦ Extension — Nullable νμ νμ₯
T? (nullable) νμ μ Extensionμ μ μ©νλ©΄ null μμ μ νΈλ¦¬ν°λ₯Ό λ§λ€ μ μμ΅λλ€.
extension OptionalExtension<T> on T? {
T orDefault(T defaultValue) => this ?? defaultValue;
R? mapIf<R>(R Function(T) mapper) =>
this != null ? mapper(this as T) : null;
void ifPresent(void Function(T) action) {
if (this != null) action(this as T);
}
}
String? nullableString = null;
print(nullableString.orDefault('κΈ°λ³Έκ°')); // κΈ°λ³Έκ°
int? nullableNumber = 42;
print(nullableNumber.orDefault(0)); // 42
String? name = 'νκΈΈλ';
int? length = name.mapIf((n) => n.length); // 3
name.ifPresent((n) => print('μλ
νμΈμ, $nλ!')); // μλ
νμΈμ, νκΈΈλλ!
μ΄λ¦ μΆ©λ ν΄κ²°
λμΌ νμ μ κ°μ μ΄λ¦μ λ©μλλ₯Ό κ°μ§ Extensionμ΄ μ¬λ¬ κ° μμΌλ©΄ μ»΄νμΌ μλ¬κ° λ°μν©λλ€. μ΄λ Extension μ΄λ¦μ λͺ μνμ¬ ν΄κ²°ν©λλ€.
extension NumberParsing on String {
int parseInt() => int.parse(this);
}
extension StringParsing on String {
int parseInt() => int.parse(this) * 2;
}
void main() {
// '42'.parseInt(); // μ»΄νμΌ μλ¬! μ΄λ€ ExtensionμΈμ§ λͺ¨νΈν¨
// Extension μ΄λ¦μ λͺ
μνμ¬ ν΄κ²°
print(NumberParsing('42').parseInt()); // 42
print(StringParsing('42').parseInt()); // 84
}
ν΅μ¬ μ 리
- ✓Extensionμ μλ³Έ ν΄λμ€λ₯Ό μμ νμ§ μκ³ κΈ°λ₯μ μΆκ°νλ κ°λ ₯ν λꡬ
- ✓getter, setter, λ©μλ, μ°μ°μ λͺ¨λ μΆκ° κ°λ₯
- ✓μ λ€λ¦ ExtensionμΌλ‘ λ€μν νμ μ λ²μ© μ νΈλ¦¬ν° ꡬν
- ✓static λ©€λ²λ Extension μ΄λ¦μΌλ‘λ§ νΈμΆ κ°λ₯
- ⚠dynamic νμ μμλ Extension λ©μλ νΈμΆ λΆκ°
- ⚠μ΄λ¦ μΆ©λ μ Extension μ΄λ¦μ λͺ μνκ±°λ import show/hideλ‘ μ μ΄
Implementation Steps
String extension β add capitalize, isValidEmail, repeat utility methods
int extension β toTimeString(), isPrime, factorial math operations
List<T> extension β generic firstOrNull, distinct, chunk(n) safe collection utils
Conflict resolution β name extensions explicitly, control with show/hide on import
Pros
- ✓ Extend functionality without modifying original class
- ✓ Improve readability by attaching util functions directly to types
Cons
- ✗ Extension methods cannot be called on dynamic types