πŸ”Œ

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

1

String extension β€” add capitalize, isValidEmail, repeat utility methods

2

int extension β€” toTimeString(), isPrime, factorial math operations

3

List<T> extension β€” generic firstOrNull, distinct, chunk(n) safe collection utils

4

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

Use Cases

BuildContext extension for easy Theme, MediaQuery access in Flutter Project-wide String/DateTime utility method collection