Skip to content

Dart 치트시트

Dart는 모든 플랫폼에서 빠른 앱을 위한 클라이언트 최적화 언어입니다.

01

기본

Hello World

main()은 Dart 프로그램의 진입점입니다

dart
// Hello World in Dart
void main() {
  print('Hello, World!');
}

/* Multi-line
   block comment */

/// Documentation comment
/// Supports Markdown
void greet(String name) {
  print('Hi, $name');
}

주석

문서 주석은 Markdown을 지원합니다

dart
var name = 'Alice';        // type inferred
String city = 'NYC';        // explicit type
final age = 30;             // runtime constant
const PI = 3.14;            // compile-time constant
final now = DateTime.now(); // OK: runtime value
// const time = DateTime.now(); // ERROR: not compile-time

const list = [1, 2, 3];     // const list (immutable)
final list2 = [4, 5, 6];    // final ref, mutable content

세미콜론

모든 문장은 세미콜론으로 끝나야 합니다

dart
int value = 42;              // non-nullable
int? nullableValue;          // nullable (default null)
print(nullableValue);        // null
print(nullableValue ?? 0);   // 0 (null coalescing)
nullableValue ??= 10;        // assign if null
print(nullableValue);        // 10

// int x = null;            // ERROR: non-nullable
String name = 'Alice';
print(name.length);          // safe, no null check

출력

print는 자동으로 줄바꿈을 추가합니다

dart
import 'dart:io';

void main() {
  print('Hello');                    // stdout with newline
  stdout.write('no newline');        // no trailing newline
  stderr.writeln('an error');        // stderr with newline

  String? input = stdin.readLineSync(); // read a line
  int? n = int.tryParse(input ?? '');   // safe parse
  print('You entered: $n');
}

변수 선언 키워드

var/final을 선호하세요

dart
var x = 10;          // inferred as int
var y = 3.14;        // inferred as double
var s = 'hi';        // inferred as String

dynamic d = 10;      // type can change
d = 'now string';    // OK
// d.foo();          // compiles, may fail at runtime

Object o = 'hello';  // supertype of all non-null types
// o.length;         // ERROR: Object has no length
print((o as String).length);  // 5 (cast)
02

변수

var 타입 추론

컴파일러가 자동으로 타입을 추론합니다

dart
int a = 42;
double b = 3.14;
num c = 10;     // num is supertype of int & double
num d = 2.71;
print(a.bitLength);          // 6
print(b.toStringAsFixed(2)); // '3.14'
print(10 ~/ 3);             // 3 (integer division)
print(10.remainder(3));     // 1
print(0xFF);                // 255 (hex)
print(1.5e3);               // 1500.0 (scientific)

final과 const

const가 final보다 더 엄격합니다

dart
var s1 = 'single';
var s2 = "double";
var s3 = '''multi
line string''';
var name = 'Alice';
print('Hi, $name');               // interpolation
print('Length: ${name.length}'); // expression interpolation

var raw = r'No escape: \n';      // raw string (literal)
var escaped = 'It\'s ok';        // escaped quote
print(s1[0]);                     // 's' (index access)

late 변수

지연 초기화, 첫 사용 전에 할당되어야 합니다

dart
bool isTrue = true;
bool isFalse = false;
print(!isTrue);           // false
print(isTrue && isFalse); // false
print(isTrue || isFalse); // true

// Only bool is allowed in conditions; no truthy/falsy
// if (1) {} // ERROR: must be bool
if ('text'.isNotEmpty) {
  print('non-empty');
}

dynamic 타입

타입 검사를 비활성화, 주의해서 사용하세요

dart
var list = [1, 2, 3];
var typed = <String>['a', 'b'];
var constList = const [1, 2, 3];
list.add(4);
list.addAll([5, 6]);
print(list.length);          // 6
print(list[0]);              // 1
print(list.sublist(1, 3));   // [2, 3]
var spread = [...list, 7];   // spread
// constList.add(0);         // ERROR: immutable

상수 생성자

const 생성자를 사용하여 컴파일 타임 상수를 생성하세요

dart
var map = {
  'name': 'Alice',
  'age': 30,
};
var typed = <String, int>{'a': 1, 'b': 2};
map['city'] = 'NYC';              // add entry
print(map['name']);               // Alice
print(map.length);                // 3
print(map.containsKey('age'));    // true
map.forEach((k, v) => print('$k: $v'));
var keys = map.keys.toList();     // [name, age, city]

Sets & Runes

Sets are unordered collections of unique elements—useful for deduplication and set operations (union, intersection, difference). Runes expose the Unicode code points of a string, which is needed for emoji and non-BMP characters stored as surrogate pairs in UTF-16.

dart
var set = {1, 2, 3};
set.add(2);              // no duplicate added
set.add(4);
print(set);              // {1, 2, 3, 4}
print(set.contains(2));  // true
print(set.intersection({2, 3, 5})); // {2, 3}

// Runes (Unicode code points)
var heart = '♥';        // ♥
print(heart);                // ♥
print('A'.codeUnitAt(0));    // 65
03

데이터 타입

숫자 타입

num은 int와 double의 상위 타입입니다

dart
print(5 + 3);   // 8
print(5 - 3);   // 2
print(5 * 3);   // 15
print(5 / 3);   // 1.6666... (double)
print(5 ~/ 3);  // 1 (integer division)
print(5 % 3);   // 2 (modulo)
print(-(5));    // -5 (unary minus)
print(2.toString()); // '2'

문자열

단일 따옴표, 이중 따옴표, 삼중 따옴표를 지원합니다

dart
var i = 5;
print(i++);  // 5 (postfix: use, then add)
print(i);    // 6
print(++i);  // 7 (prefix: add, then use)
print(i--);  // 7
print(--i);  // 5

불린 타입

true와 false 값만 가능합니다

dart
print(3 == 3);   // true
print(3 != 4);   // true
print(3 < 4);    // true
print(3 > 4);    // false
print(3 <= 3);   // true
print(3 >= 4);   // false
print('a' == 'a'); // true (content equality)
var l1 = [1, 2];
var l2 = [1, 2];
print(l1 == l2); // false (reference equality)

List

다른 언어의 배열과 유사합니다

dart
bool a = true, b = false;
print(a && b);  // false
print(a || b);  // true
print(!a);      // false

// short-circuit evaluation
bool check() { print('called'); return true; }
false && check();  // check() NOT called
true || check();   // check() NOT called

Map

키-값 쌍 컬렉션

dart
Object x = 'hello';
print(x is String);   // true
print(x is! int);     // true
if (x is String) {
  print(x.length);    // smart-cast to String
}
Object y = 42;
print((y as int) + 1); // 43 (cast)
// (y as String);      // runtime TypeError

Set

고유 요소의 순서 없는 컬렉션

dart
class Builder {
  String? name;
  int? size;
  Builder setName(String n) { name = n; return this; }
  Builder setSize(int s) { size = s; return this; }
}

var b = Builder()
  ..setName('widget')
  ..setSize(10);
print(b.name); // widget

String? path;
print(path?.length);     // null (safe access)
print(path?.length ?? 0); // 0
04

연산자

산술 연산자

~/는 Dart의 정수 나눗셈 연산자입니다

dart
int score = 85;
if (score >= 90) {
  print('A');
} else if (score >= 80) {
  print('B');
} else {
  print('C');
}

// ternary expression
var grade = score >= 60 ? 'pass' : 'fail';
print(grade); // pass

// if (score) {} // ERROR: condition must be bool

증감 연산자

전위: 연산 후 할당, 후위: 할당 후 연산

dart
for (var i = 0; i < 3; i++) {
  print(i);
}

var list = ['a', 'b', 'c'];
for (var item in list) {
  print(item);
}

// for-in with Map entries
var map = {'x': 1, 'y': 2};
for (var entry in map.entries) {
  print('${entry.key}: ${entry.value}');
}

타입 검사 연산자

as는 타입 캐스팅을 수행합니다

dart
var i = 0;
while (i < 3) {
  print('while $i');
  i++;
}

var j = 0;
do {
  print('do $j');
  j++;
} while (j < 3);

조건식

??는 null 병합 연산자입니다

dart
// Classic switch statement
var color = 'red';
switch (color) {
  case 'red':
    print('stop');
    break;
  case 'green':
    print('go');
    break;
  default:
    print('unknown');
}

// Dart 3 switch expression
String describe(int n) => switch (n) {
  0 => 'zero',
  1 || 2 => 'small',
  >= 3 && <= 10 => 'medium',
  _ => 'large',
};
print(describe(5)); // medium

캐스케이드 연산자

..는 객체 자신을 반환하는 체인 호출을 허용합니다

dart
for (var i = 0; i < 5; i++) {
  if (i == 2) continue; // skip 2
  if (i == 4) break;    // stop at 4
  print(i);  // 0, 1, 3
}

// labels for nested loops
outer:
for (var i = 0; i < 3; i++) {
  for (var j = 0; j < 3; j++) {
    if (i + j > 2) break outer;
    print('$i,$j');
  }
}

assert

assert(condition, message) checks a condition during development. Assertions are enabled in debug mode and removed in production (release) builds. Use them for internal invariants and debugging—not for input validation that must run in production.

dart
void setAge(int age) {
  assert(age >= 0, 'age must be non-negative');
  // ...
}

assert(1 == 1);  // passes in debug
// assert(1 == 2, 'math is broken'); // fails in debug

// Assertions are stripped in release/production builds
05

제어 흐름

if-else

조건은 bool 타입이어야 합니다

dart
// named function with return type
int add(int a, int b) {
  return a + b;
}

// functions are first-class objects
int Function(int) makeAdder(int n) {
  return (int x) => x + n;
}

var add5 = makeAdder(5);
print(add5(3)); // 8

print(add(2, 3)); // 5

for 루프

C 스타일과 for-in을 지원합니다

dart
// single-expression function with =>
int square(int x) => x * x;
String greet(String name) => 'Hi, $name';

// arrow with nullable
String? firstChar(String? s) => s?.isEmpty ?? true ? null : s[0];

print(square(4));      // 16
print(greet('Al'));    // Hi, Al
print(firstChar('hi')); // h

while 루프

do-while은 최소 한 번 실행합니다

dart
// optional positional params wrapped in []
String greet(String name, [String? title]) {
  if (title != null) {
    return 'Hello, $title $name';
  }
  return 'Hello, $name';
}

print(greet('Alice'));        // Hello, Alice
print(greet('Bob', 'Dr.'));   // Hello, Dr. Bob

switch

Dart 3은 switch 표현식을 지원합니다

dart
// named params wrapped in {}, required marks mandatory
void createUser({
  required String name,
  int age = 0,
  String? email,
}) {
  print('$name, $age, $email');
}

createUser(name: 'Alice', age: 30);
createUser(name: 'Bob', email: '[email protected]');

// named params are passed by name, order-independent

break와 continue

break는 루프를 종료, continue는 이번 반복을 건너뜁니다

dart
// default values for optional positional
double calc(double a, [double b = 1.0, double c = 0.0]) {
  return a * b + c;
}

// default values for named
void config({String host = 'localhost', int port = 8080}) {
  print('$host:$port');
}

print(calc(5));       // 5.0
print(calc(5, 2));    // 10.0
config(port: 3000);   // localhost:3000

Anonymous Functions & Closures

Anonymous functions (lambdas) have no name and are often assigned to variables or passed as callbacks. Closures capture variables from their enclosing scope and keep them alive. The parameter list can be typed (int a, int b) or untyped (a, b).

dart
// anonymous function assigned to a variable
var multiply = (int a, int b) => a * b;
print(multiply(3, 4)); // 12

// closure capturing a variable
Function counter() {
  int count = 0;
  return () => ++count;
}

var c = counter();
print(c()); // 1
print(c()); // 2

// used as callbacks
[1, 2, 3].forEach((n) => print(n));
06

함수

함수 선언

Dart에서 함수는 일급 객체입니다

dart
class Person {
  String name;
  int age;

  // constructor
  Person(this.name, this.age);

  void greet() {
    print('Hi, I am $name');
  }
}

var p = Person('Alice', 30);
p.greet(); // Hi, I am Alice
print(p.name); // Alice

화살표 함수

=>는 단일 표현식 함수에 사용됩니다

dart
class Point {
  double x;
  double y;

  Point(this.x, this.y);

  double distanceTo(Point other) {
    return ((x - other.x) * (x - other.x) +
            (y - other.y) * (y - other.y));
  }

  void moveBy(double dx, double dy) {
    this.x += dx;   // 'this' is optional when unambiguous
    y += dy;
  }
}

선택적 매개변수

[]는 위치 선택적 매개변수를 감쌉니다

dart
class Rectangle {
  double width, height;
  Rectangle(this.width, this.height);

  // computed getter
  double get area => width * height;
  set size(double v) {
    width = v;
    height = v;
  }
}

var r = Rectangle(3, 4);
print(r.area);  // 12 (accessed like a field)
r.size = 10;
print(r.area);  // 100

이름 있는 매개변수

{}는 이름 있는 매개변수를 감싸며, required는 필수를 나타냅니다

dart
class MathUtils {
  static const double PI = 3.14159;
  static double circleArea(double r) => PI * r * r;
}

// accessed via the class, not an instance
print(MathUtils.PI);             // 3.14159
print(MathUtils.circleArea(2));  // 12.566

// static members belong to the class, not instances

기본값

선택적 매개변수는 기본값을 가질 수 있습니다

dart
class Logger {
  final String name;
  static final Map<String, Logger> _cache = {};

  // factory may return a cached instance
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  Logger._internal(this.name);
}

var a = Logger('app');
var b = Logger('app');
print(identical(a, b)); // true (same cached instance)

익명 함수

익명 함수는 종종 콜백으로 사용됩니다

dart
class Book {
  String title;
  Book(this.title);

  @override
  String toString() => 'Book($title)';

  @override
  bool operator ==(Object other) =>
      other is Book && other.title == title;

  @override
  int get hashCode => title.hashCode;
}

print(Book('Dart')); // Book(Dart)
print(Book('A') == Book('A')); // true
07

클래스

클래스 정의

생성자는 클래스와 같은 이름을 가집니다

dart
class Animal {
  String species;

  // generative constructor
  Animal(this.species);

  // if no constructor is written, Dart provides:
  // Animal() : species = 'unknown';
}

var a = Animal('cat');
print(a.species); // cat

이름 있는 생성자

클래스는 여러 이름 있는 생성자를 가질 수 있습니다

dart
class Point {
  double x, y;
  Point(this.x, this.y);

  // named constructor
  Point.origin() : x = 0, y = 0;
  Point.fromList(List<double> l) : x = l[0], y = l[1];
}

var p1 = Point.origin();
var p2 = Point.fromList([3, 4]);
print('${p1.x},${p1.y}'); // 0.0,0.0
print('${p2.x},${p2.y}'); // 3.0,4.0

게터와 세터

get/set 키워드를 사용하세요

dart
class Temperature {
  final double celsius;

  // initializer list runs before body
  Temperature(double c) : celsius = c;
  Temperature.fromFahrenheit(double f)
      : celsius = (f - 32) * 5 / 9;

  // assert in initializer list
  Temperature.clamped(double c)
      : assert(c >= -273.15),
        celsius = c < -273.15 ? -273.15 : c;
}

print(Temperature.fromFahrenheit(32).celsius); // 0.0

정적 멤버

static 멤버는 인스턴스가 아닌 클래스에 속합니다

dart
class Point {
  double x, y;
  Point(this.x, this.y);

  // redirect to another constructor with 'this'
  Point.alongX(double x) : this(x, 0);
  Point.origin() : this(0, 0);
  Point.fromDouble(double n) : this.alongX(n);
}

print(Point.alongX(5).y); // 0.0
print(Point.origin().x);  // 0.0

팩토리 생성자

factory는 항상 새 인스턴스를 생성하지는 않습니다

dart
class ImmutablePoint {
  final double x;
  final double y;

  // const constructor: creates compile-time constant instances
  const ImmutablePoint(this.x, this.y);

  static const origin = ImmutablePoint(0, 0);
}

const p = ImmutablePoint(1, 2);
const o = ImmutablePoint.origin;
print(identical(o, ImmutablePoint(0, 0))); // true

Factory & Caching

Factory constructors can return const or cached instances and may use Dart 3 switch expressions. They differ from generative constructors which always create a new instance. Use factories when construction logic must choose what to return.

dart
class Shape {
  final String type;
  const Shape._(this.type);

  factory Shape(String kind) {
    return switch (kind) {
      'circle' => const Shape._('circle'),
      'square' => const Shape._('square'),
      _ => const Shape._('unknown'),
    };
  }
}

print(Shape('circle').type); // circle
print(identical(Shape('circle'), Shape('circle'))); // true
08

상속

extends 상속

Dart는 단일 상속입니다

dart
class Animal {
  String name;
  Animal(this.name);

  void speak() => print('$name makes a sound');
}

class Dog extends Animal {
  Dog(String name) : super(name);

  @override
  void speak() => print('$name barks');
}

var d = Dog('Rex');
d.speak(); // Rex barks

super 호출

@override는 메서드 재정의를 표시합니다

dart
class Vehicle {
  int speed = 0;
  void accelerate(int by) => speed += by;
  void describe() => print('Vehicle at $speed');
}

class Car extends Vehicle {
  @override
  void accelerate(int by) {
    super.accelerate(by);  // call parent method
    print('Car now at $speed');
  }
}

Car().accelerate(10); // Car now at 10

추상 클래스

추상 클래스는 인스턴스화할 수 없습니다

dart
class Base {
  String tag;
  Base(this.tag) {
    print('Base created: $tag');
  }
}

class Derived extends Base {
  Derived(String tag) : super(tag) {
    print('Derived created');
  }
}

// order: super initializer -> super body -> derived body
Derived('x');
// Base created: x
// Derived created

인터페이스 구현

모든 클래스는 암묵적으로 인터페이스를 정의합니다

dart
class Proxy implements Object {
  @override
  dynamic noSuchMethod(Invocation inv) {
    print('Called: ${inv.memberName}');
    return null;
  }
}

var p = Proxy();
p.someMissingMethod(); // Called: Symbol("someMissingMethod")

noSuchMethod

존재하지 않는 메서드 호출을 처리합니다

dart
sealed class Shape {
  const Shape();
}

class Circle extends Shape {
  final double r;
  const Circle(this.r);
}
class Square extends Shape {
  final double s;
  const Square(this.s);
}

// switch is exhaustive over sealed subtypes
double area(Shape s) => switch (s) {
  Circle(:var r) => 3.14 * r * r,
  Square(:var s) => s * s,
};

print(area(const Circle(2))); // 12.56
09

Mixins

Mixin 정의

mixin은 생성자를 가질 수 없습니다

dart
abstract class Animal {
  // abstract method: no body, must be overridden
  void makeSound();

  // concrete method: inherited as-is
  void breathe() => print('breathing');
}

class Cat extends Animal {
  @override
  void makeSound() => print('meow');
}

var c = Cat();
c.makeSound(); // meow
// Animal();    // ERROR: cannot instantiate abstract class

Mixin 사용

with 키워드로 여러 mixin을 사용하세요

dart
class Television {
  void turnOn() => print('on');
  void turnOff() => print('off');
}

// every class implicitly defines an interface
class SmartTV implements Television {
  @override
  void turnOn() => print('smart on');
  @override
  void turnOff() => print('smart off');
}

SmartTV().turnOn(); // smart on

mixin 제약

on은 mixin을 특정 클래스로 제한합니다

dart
abstract class Flyer {
  void fly();
}
abstract class Swimmer {
  void swim();
}

// implement multiple interfaces
class Duck implements Flyer, Swimmer {
  @override
  void fly() => print('flying');
  @override
  void swim() => print('swimming');
}

Duck().fly();   // flying
Duck().swim();  // swimming

mixin class

Dart 3은 mixin class를 지원합니다

dart
class Base {
  void greet() => print('hello');
  void wave() => print('waving');
}

// extends: reuse implementation, single parent
class A extends Base {
  @override
  void greet() => print('A says hi');
}

// implements: contract only, must override all
class B implements Base {
  @override
  void greet() => print('B says hi');
  @override
  void wave() => print('B waving');
}

Abstract vs Interface Guidelines

Use abstract classes to share implementation among closely related types (inheritance of code). Use interfaces (abstract classes used via implements) to define capabilities or contracts that unrelated types can fulfill. Dart merges these concepts: an abstract class can serve as both.

dart
// Abstract class: share code among related types
abstract class Repository<T> {
  T? find(int id);          // abstract
  void save(T item) => print('saved'); // shared
}

// Interface: define a capability
abstract class Comparable<T> {
  int compareTo(T other);
}

class Product extends Repository<Product>
    implements Comparable<Product> {
  @override
  Product? find(int id) => null;
  @override
  int compareTo(Product other) => 0;
}
10

Async/Await

async 함수

async는 비동기 함수를 표시하며, Future를 반환합니다

dart
mixin Greeter {
  String get name;

  void greet() => print('Hello, $name!');
}

class User with Greeter {
  @override
  String name;
  User(this.name);
}

User('Alice').greet(); // Hello, Alice!

await

await는 async 함수 내에서만 사용할 수 있습니다

dart
mixin Walker {
  void walk() => print('walking');
}
mixin Talker {
  void talk() => print('talking');
}

// mix in multiple mixins
class Person extends Object with Walker, Talker {
  String name;
  Person(this.name);
}

var p = Person('Bob');
p.walk(); // walking
p.talk(); // talking

try-catch 비동기

비동기 오류는 try-catch로 잡습니다

dart
mixin Musician on Performer {
  void playNote() => print('playing note');
}

abstract class Performer {
  void perform();
}

class Singer extends Performer with Musician {
  @override
  void perform() => print('singing');
}

Singer().playNote(); // playing note
// Musician can only be mixed into Performer subtypes

Future.wait 병렬

여러 Future를 병렬로 실행합니다

dart
// 'mixin class' can be both extended and mixed in
mixin class Counter {
  int _count = 0;
  int get count => _count;
  void increment() => _count++;
}

class App extends Counter {}
class Tool with Counter {}

print(App().count); // 0
App().increment();
Tool().increment();
print(App().count); // 0 (separate instance)

async for 루프

await for는 Stream을 소비합니다

dart
mixin A {
  void hello() => print('A');
}
mixin B {
  void hello() => print('B');
}

class X with A, B {}
class Y with B, A {}

X().hello(); // B (later mixin wins)
Y().hello(); // A (later mixin wins)

// resolution order: class -> last mixin -> ... -> first mixin
11

Streams

Stream 생성

async*는 Stream을 생성하고, yield는 값을 내보냅니다

dart
class Stack<T> {
  final List<T> _items = [];

  void push(T item) => _items.add(item);
  T pop() => _items.removeLast();
  bool get isEmpty => _items.isEmpty;
}

var s = Stack<int>();
s.push(1);
s.push(2);
print(s.pop()); // 2

var names = Stack<String>();
names.push('Al');

Stream 수신

listen은 StreamSubscription을 반환합니다

dart
// generic method with its own type parameter
T firstOrDefault<T>(List<T> list, T defaultValue) {
  return list.isEmpty ? defaultValue : list.first;
}

print(firstOrDefault<int>([1, 2, 3], 0)); // 1
print(firstOrDefault([], 'none'));        // none (type inferred)

// type often inferred from arguments

Stream 메서드

Stream은 다양한 편의 메서드를 제공합니다

dart
class Comparable<T> {
  int compareTo(T other);
}

// constrain T to subtypes of Comparable<T>
T max<T extends Comparable<T>>(T a, T b) {
  return a.compareTo(b) >= 0 ? a : b;
}

// number sum constraint
num sum<T extends num>(List<T> nums) =>
    nums.fold(0, (a, b) => a + b);

print(sum([1, 2.5, 3])); // 6.5

StreamController

Stream의 데이터 흐름을 수동으로 제어합니다

dart
List<int> nums = [1, 2, 3];
Map<String, int> scores = {'a': 1, 'b': 2};
Set<double> uniq = {1.1, 2.2, 1.1};

// runtime type checks work (reified generics)
print(nums is List<int>);    // true
print(nums is List<String>); // false
print(scores is Map);        // true

// generic variance
void process(List<num> list) => print(list);
process(nums); // OK: int is a num

Stream 변환

List의 체인 연산과 유사합니다

dart
// generic function type alias
typedef Transformer<T> = T Function(T input);

int doubler(int x) => x * 2;
String upper(String s) => s.toUpperCase();

Transformer<int> dt = doubler;
Transformer<String> ut = upper;
print(dt(5));  // 10
print(ut('hi')); // HI

// generic class alias
typedef IntList = List<int>;
IntList xs = [1, 2, 3];
12

Futures

Future 생성

Future는 비동기 결과를 나타냅니다

dart
var nums = [3, 1, 2];
nums.sort();
print(nums);            // [1, 2, 3]
print(nums.reversed.toList()); // [3, 2, 1]
print(nums.indexOf(2)); // 1
print(nums.contains(3));// true
print(nums.where((n) => n > 1).toList()); // [2, 3]
print(nums.map((n) => n * 2).toList());   // [2, 4, 6]
print(nums.fold(0, (a, b) => a + b));     // 6

then 체이닝

then은 새 Future를 반환합니다

dart
var a = {1, 2, 3};
var b = {2, 3, 4};
print(a.union(b));        // {1, 2, 3, 4}
print(a.intersection(b)); // {2, 3}
print(a.difference(b));   // {1}

var dedup = [1, 1, 2, 3, 3].toSet();
print(dedup.toList());    // [1, 2, 3]
print(dedup.contains(2)); // true

Future.delayed

지연 실행

dart
var ages = {'Alice': 30, 'Bob': 25};
ages['Carol'] = 28;
print(ages.keys);    // (Alice, Bob, Carol)
print(ages.values);  // (30, 25, 28)
print(ages.length);  // 3

ages.update('Bob', (v) => v + 1);
print(ages['Bob']);  // 26

ages.remove('Alice');
ages.forEach((k, v) => print('$k=$v'));

var mapped = ages.map((k, v) => MapEntry(k, v + 100));

Future.any

가장 먼저 완료된 Future의 결과를 반환합니다

dart
var a = [1, 2];
var b = [0, ...a, 3];      // [0, 1, 2, 3]
print(b);

List<int>? maybe;
var c = [0, ...?maybe, 4]; // [0, 4] (null-spread is safe)
print(c);

var m1 = {'a': 1};
var m2 = {'b': 2, ...m1};  // {b: 2, a: 1}
print(m2);

Completer

Future를 수동으로 완료합니다

dart
var promo = true;
var menu = [
  'home',
  'products',
  if (promo) 'sale',
  'about',
];
print(menu); // [home, products, sale, about]

var nums = [1, 2, 3];
var doubled = [
  for (var n in nums) n * 2,
];
print(doubled); // [2, 4, 6]

// combine: [for (var x in xs) if (x > 0) x]

Higher-order Methods

Lists/Iterables support any, every, firstWhere, reduce, fold, skip, take, expand, and more. fold is powerful—it carries an accumulator of any type. These higher-order methods enable declarative, functional-style data processing without explicit loops.

dart
var nums = [1, 2, 3, 4, 5];
print(nums.any((n) => n > 4));   // true
print(nums.every((n) => n > 0)); // true
print(nums.firstWhere((n) => n > 2)); // 3
print(nums.reduce((a, b) => a + b));  // 15
var byParity = nums.fold(<bool, List<int>>{}, (m, n) {
  m[n.isOdd] = [...?m[n.isOdd], n]; return m;
});
print(byParity); // {true: [1,3,5], false: [2,4]}
13

컬렉션

List 연산

List는 중복을 허용하는 순서 있는 컬렉션입니다

dart
Future<String> fetchUser() {
  return Future.delayed(Duration(seconds: 1), () => 'Alice');
}

void main() {
  fetchUser().then((name) {
    print('Got: $name'); // Got: Alice (after 1s)
  });
  print('waiting...');
}

Set 연산

Set은 고유 요소의 순서 없는 컬렉션입니다

dart
Future<String> fetchUser() async {
  await Future.delayed(Duration(seconds: 1));
  return 'Alice';
}

Future<void> main() async {
  print('start');
  String name = await fetchUser();
  print('Got: $name');
  print('done');
}

Map 연산

Map은 키-값 쌍 컬렉션입니다

dart
Future<int> divide(int a, int b) async {
  if (b == 0) throw Exception('divide by zero');
  return a ~/ b;
}

Future<void> main() async {
  try {
    var result = await divide(10, 0);
    print(result);
  } catch (e) {
    print('Error: $e'); // Error: Exception: divide by zero
  } finally {
    print('done');
  }
}

컬렉션 전개

... 전개 연산자

dart
Future<int> compute() async => 42;

compute()
    .then((v) => v * 2)
    .then((v) => print(v))      // 84
    .catchError((e) => print('err: $e'))
    .whenComplete(() => print('cleanup'));

// chaining transforms the result type
Future<String> fetch() async => 'data';
fetch().then((s) => s.length).then(print); // 4

collection-if와 collection-for

Dart 특정의 컬렉션 내 조건/루프

dart
Future<int> task(int n) async {
  await Future.delayed(Duration(milliseconds: n));
  return n;
}

// run in parallel, wait for all
var all = await Future.wait([task(100), task(50), task(200)]);
print(all); // [100, 50, 200]

// resolve with the first to complete
var first = await Future.any([task(100), task(50), task(200)]);
print(first); // 50 (fastest)

Completer

A Completer lets you manually create and complete a Future. Call complete(value) or completeError(error) to finish it. Completers are useful when wrapping callback-based APIs into Futures, or when a Future's completion is triggered by an external event you control.

dart
import 'dart:async';

Completer<String> completer = Completer<String>();

// complete the future from elsewhere
Future<String> get value => completer.future;

completer.complete('resolved!');

void main() async {
  print(await value); // resolved!
}

// useful when bridging callback-based APIs to Futures
14

문자열 메서드

문자열 보간

$variable 또는 ${expression}

dart
Stream<int> countDown(int from) async* {
  while (from > 0) {
    await Future.delayed(Duration(seconds: 1));
    yield from;
    from--;
  }
}

void main() async {
  await for (var n in countDown(3)) {
    print(n); // 3, 2, 1 (one per second)
  }
}

일반 메서드

문자열은 불변입니다

dart
var sub = countDown(3).listen(
  (n) => print('got $n'),
  onDone: () => print('done'),
  onError: (e) => print('err: $e'),
);

// pause/resume/cancel
sub.pause();
sub.resume();
// sub.cancel(); // stop listening

Stream<int> countDown(int from) async* {
  while (from > 0) yield from--;
}

부분 문자열

인덱스는 0부터 시작합니다

dart
var stream = Stream.fromIterable([1, 2, 3, 4]);

// transform like an Iterable
var evens = stream.where((n) => n.isEven);
var doubled = stream.map((n) => n * 2);

await for (var n in Stream.fromIterable([1,2,3]).map((n) => n * 10)) {
  print(n); // 10, 20, 30
}

print(await stream.first);  // 1
print(await stream.last);   // 4
print(await stream.length); // 4

교체와 분할

정규식 교체를 지원합니다

dart
import 'dart:async';

var controller = StreamController<int>();

// add events manually
controller.add(1);
controller.add(2);
controller.addError('oops');
controller.close();

controller.stream.listen(
  (n) => print(n),           // 1, 2
  onError: (e) => print(e),  // oops
  onDone: () => print('done'),
);

// use controller.addError/sink.add for errors

StringBuilder

무거운 연결에는 StringBuffer를 사용하세요

dart
// await for consumes a stream like a loop
Future<int> sumStream(Stream<int> s) async {
  var total = 0;
  await for (var n in s) {
    total += n;
  }
  return total;
}

print(await sumStream(Stream.fromIterable([1, 2, 3]))); // 6

// broadcast stream: multiple listeners
var bc = StreamController<int>.broadcast();
bc.stream.listen(print);
bc.stream.listen((n) => print('got $n'));
bc.add(5); // both listeners receive 5
15

예외 처리

throw

모든 객체를 던질 수 있습니다

dart
void checkAge(int age) {
  if (age < 0) {
    throw ArgumentError('age cannot be negative');
  }
  if (age > 150) {
    throw StateError('unrealistic age: $age');
  }
}

// you can throw any non-null object
void fail() => throw 'something went wrong';

try-catch-finally

on은 특정 예외 타입을 잡습니다

dart
try {
  checkAge(-5);
} on ArgumentError catch (e) {
  print('argument error: $e');
} on StateError catch (e) {
  print('state error: $e');
} catch (e, stackTrace) {
  print('unknown: $e');
  print(stackTrace);
} finally {
  print('always runs');
}

사용자 정의 예외

Exception 인터페이스를 구현하세요

dart
class InvalidCredentialsException implements Exception {
  final String message;
  InvalidCredentialsException(this.message);

  @override
  String toString() => 'InvalidCredentialsException: $message';
}

void login(String user, String pass) {
  if (user.isEmpty) {
    throw InvalidCredentialsException('username required');
  }
}

try {
  login('', 'x');
} on InvalidCredentialsException catch (e) {
  print(e); // InvalidCredentialsException: username required
}

rethrow

rethrow는 예외를 다시 던집니다

dart
Future<void> logErrors(Future<void> Function() action) async {
  try {
    await action();
  } catch (e) {
    print('logging error: $e');
    rethrow; // re-throw the caught exception
  }
}

void main() async {
  try {
    await logErrors(() async => throw Exception('fail'));
  } catch (e) {
    print('handled upstream: $e');
  }
}

Error vs Exception

Exception is for runtime conditions a program can reasonably catch and recover from (e.g., network failure, bad input). Error represents programming bugs (type errors, assertion failures, index out of range) that should be fixed in code, not caught at runtime. Catching Errors is discouraged.

dart
// Exception: recoverable, expected to be caught
class MyException implements Exception {}

// Error: programming bugs, not meant to be caught
class MyError extends Error {
  @override
  String toString() => 'MyError: invalid state';
}

void risky() {
  throw MyError();   // bug: should fix the code
  throw MyException(); // runtime condition: catch it
}

// assert failures, type errors, range errors are Errors
16

Enums

기본 Enum

Enum 값은 name과 index를 가집니다

dart
// alias for a function type
typedef IntOperator = int Function(int, int);

int add(int a, int b) => a + b;
int mul(int a, int b) => a * b;

IntOperator op = add;
print(op(2, 3)); // 5
op = mul;
print(op(2, 3)); // 6

// pass as a parameter
void apply(IntOperator f, int a, int b) => print(f(a, b));

강화된 Enum (Dart 3)

Enum은 필드와 메서드를 가질 수 있습니다

dart
// generic function type alias
typedef Mapper<T, R> = R Function(T input);

String stringify(int n) => n.toString();
int lenOf(String s) => s.length;

Mapper<int, String> intToStr = stringify;
Mapper<String, int> strToLen = lenOf;

print(intToStr(42));  // '42'
print(strToLen('hi')); // 2

Enum 반복

values는 모든 enum 값을 반환합니다

dart
// Dart 2.13+: alias for any type, not just functions
typedef IntList = List<int>;
typedef StringMap<V> = Map<String, V>;

IntList nums = [1, 2, 3];
StringMap<int> scores = {'a': 1};

// alias for a record type (Dart 3)
typedef Point = ({double x, double y});
Point p = (x: 1.0, y: 2.0);
print(p.x); // 1.0

switch Enum

Dart 3은 break가 필요 없습니다

dart
typedef Predicate<T> = bool Function(T);

bool isEven(int n) => n.isEven;

List<T> filter<T>(List<T> list, Predicate<T> test) {
  return list.where(test).toList();
}

print(filter([1, 2, 3, 4], isEven)); // [2, 4]
print(filter(['', 'a', ''], (s) => s.isNotEmpty)); // [a]

// typedef makes callback contracts explicit

typedef vs inline Function types

A typedef is just an alias—it's identical to the inline function type at runtime and for type checking. typedef improves readability and centralizes the contract so changes happen in one place. Prefer typedef for any function type used in more than one location.

dart
// these two are equivalent
typedef Handler = void Function(String event);

class EventBus {
  // using typedef
  void on(Handler handler) {}
  // using inline type
  void on2(void Function(String) handler) {}
}

// both accept the same functions
void myHandler(String e) => print(e);
EventBus().on(myHandler);
EventBus().on2(myHandler);
17

제네릭

제네릭 클래스

T는 타입 매개변수입니다

dart
enum Color { red, green, blue }

var c = Color.red;
print(c);           // Color.red
print(c.name);      // 'red'
print(c.index);     // 0
print(Color.values); // [Color.red, Color.green, Color.blue]
print(Color.green.index); // 1

제네릭 메서드

메서드도 타입 매개변수를 가질 수 있습니다

dart
enum Vehicle {
  car('Car', 4),
  bike('Bike', 2),
  truck('Truck', 6);

  final String label;
  final int wheels;
  const Vehicle(this.label, this.wheels);

  int get axles => wheels ~/ 2;
}

print(Vehicle.car.label);   // Car
print(Vehicle.bike.wheels); // 2
print(Vehicle.truck.axles); // 3

제네릭 제약

extends는 타입 매개변수를 제한합니다

dart
enum Status { pending, active, done }

// iterate all values
for (var s in Status.values) {
  print(s.name);
}

// exhaustive switch (no default needed)
String label(Status s) => switch (s) {
  Status.pending => 'Waiting',
  Status.active => 'Running',
  Status.done => 'Finished',
};

print(label(Status.active)); // Running

제네릭 컬렉션

컬렉션은 제네릭을 광범위하게 사용합니다

dart
enum HttpStatus {
  ok(200),
  notFound(404),
  serverError(500);

  final int code;
  const HttpStatus(this.code);

  bool get isSuccess => code >= 200 && code < 300;
  String get reason => switch (this) {
    ok => 'OK',
    notFound => 'Not Found',
    serverError => 'Internal Server Error',
  };
}

print(HttpStatus.ok.isSuccess);   // true
print(HttpStatus.notFound.reason); // Not Found

Enum Comparison

Each enum value is a singleton—there's exactly one instance per value per program. == compares by identity effectively. index allows ordering by declaration position. Use == for equality; identical() also works since values are canonicalized. Enums make great Map keys and Set elements.

dart
enum Priority { low, medium, high }

var a = Priority.low;
var b = Priority.high;

print(a == b);           // false
print(a == Priority.low);// true
print(a.index < b.index);// true
print(identical(a, Priority.low)); // true

// enums are singletons: only one instance per value
// use == for equality, not identical (though both work)
18

Typedefs

함수 타입 별칭

함수 타입의 별칭을 생성합니다

dart
class Animal {
  @override
  String toString() => 'Animal';

  @Deprecated('use newName instead')
  String oldName = 'x';
  String newName = 'x';

  @protected
  void internalMethod() {}

  @visibleForTesting
  String testHook() => 'test';
}

제네릭 typedef

제네릭 매개변수를 지원합니다

dart
class Base {
  void greet() {}
  String name = 'base';
}

class Derived extends Base {
  @override
  void greet() => print('hi');

  // @override verifies the parent method exists
  // typo here would be a compile error:
  // @override void greeet() {}
}

// @Deprecated emits a warning at the call site
@Deprecated('use bar()')
void foo() {}
void bar() {}

새 스타일 typedef

Dart 2.13+은 비함수 타입 별칭을 지원합니다

dart
// a custom annotation is just a const constructor class
class Todo {
  final String msg;
  const Todo(this.msg);
}

class Service {
  @Todo('refactor to use cache')
  void fetchData() {}

  @Todo('add tests')
  void process() {}
}

// annotations are accessed via dart:mirrors (VM) or
// code generation (build_runner) in practice

@immutable & @JsonSerializable

@immutable (from package:meta) marks a class whose instances should not change after construction; subclasses and fields should be final. @JsonSerializable (from json_serializable) triggers code generation for JSON conversion via build_runner. Annotations drive many Dart/Flutter ecosystems.

dart
import 'package:meta/meta.dart';
// requires the 'meta' package

@immutable
class User {
  final String name;
  final int age;
  const User(this.name, this.age);
}

// with package:json_annotation / json_serializable
// @JsonSerializable()
// class Product {
//   final String id;
//   Product(this.id);
//   factory Product.fromJson(Map<String, dynamic> j) => ...;
// }

Annotation on Parameters

Annotations can be placed on parameters, library declarations, and typedefs too. Before null safety, @required marked mandatory named params; the modern equivalent is the 'required' keyword. Annotations on parameters are widely used by serialization and DI frameworks.

dart
class Required {
  const Required([this.reason]);
  final String? reason;
}

class Service {
  // annotation on a parameter
  void create({
    @Required('name is mandatory') String? name,
    @protected int? internal,
  }) {
    print(name);
  }
}

// @required is built into Dart (the 'required' keyword
// is preferred in Dart 2.12+ for null safety)
19

Null 안전

Nullable 타입

?는 nullable 타입을 나타냅니다

dart
// import an entire library
import 'dart:io';
import 'package:http/http.dart';

// import only specific names
import 'dart:math' show Random, pi;

// hide specific names
import 'dart:async' hide Timer;

// prefix to avoid name clashes
import 'package:http/http.dart' as http;
http.get(Uri.parse('https://example.com'));

Null 단언

!는 non-null을 단언, 주의해서 사용하세요

dart
// library.dart
library my_lib;

// split implementation across files
part 'src/widget_a.dart';
part 'src/widget_b.dart';

// re-export another library's API
export 'src/utils.dart' show formatDate, parseDate;

// users import library.dart and get everything

Null 안전 연산자

?. ?? ??= null 안전 연산

dart
// names starting with _ are library-private
class _Internal {
  void _helper() {}
}

class Public {
  String _secret = 'hidden';   // private field
  String name = 'visible';     // public field

  String _process() => 'internal';
  String reveal() => _process();
}

// _secret is accessible anywhere in the SAME library/file
// but not from other libraries that import this file

타입 승격

null 검사 후 자동 타입 승격

dart
// load a library on demand (web only)
import 'package:heavy_lib/heavy.dart' deferred as heavy;

Future<void> main() async {
  // library is NOT loaded until this call
  await heavy.loadLibrary();
  heavy.SomeClass().doWork();
}

// useful for splitting large web bundles and
// loading rarely-used features only when needed

late와 null

late는 non-null 변수의 초기화를 지연시킵니다

dart
// explicit library declaration
library my_package.utils;

import 'dart:math';

part 'src/helper.dart';

const double version = 1.0;

// a 'library' name is optional in modern Dart;
// it's mainly used with part/part-of and tooling
// most files omit it and are treated as anonymous libraries

part & part of

part/part-of splits one library across files: the main file declares part 'file.dart'; the part file declares 'part of library;'. Parts share the library's scope including private (_name) members. Prefer separate libraries with export for new code—parts are for tightly-coupled implementations.

dart
// shapes.dart
library shapes;
part 'circle.dart';
part 'square.dart';

class Shape {}

// circle.dart
part of shapes;
class Circle extends Shape {}

// files in the same library share private members
// (_name visible across all parts)
20

Extensions

확장 메서드

기존 타입에 기능을 추가합니다

dart
int a = 42;          // non-nullable: cannot be null
int? b;              // nullable: can be null
print(b);            // null
b = 10;
print(b);            // 10

String name = 'Al';  // non-nullable
String? middle;      // nullable

// non-nullable types are guaranteed non-null
// int x = null;      // ERROR
// print(a.length);   // safe: a is non-null

Extension 사용

일반 메서드처럼 호출합니다

dart
String? maybeName;
// int len = maybeName.length; // ERROR: maybeName is nullable

maybeName = 'Alice';
int len = maybeName!.length;  // ! asserts non-null
print(len); // 5

// throws if null at runtime:
// String? n; print(n!.length); // NoSuchMethodError/null

제네릭 Extension

제네릭 타입 매개변수를 지원합니다

dart
String? name;
print(name?.length);     // null (safe access)
print(name?.length ?? 0);// 0 (default if null)

name = 'Alice';
print(name?.length);     // 5

name ??= 'Bob';          // assign only if null
print(name);             // Alice (already set)

List<int>? list;
print(list?.first);      // null
print(list?.first ?? -1);// -1

Type Promotion

Type promotion: after a null check (x != null) or type check (x is String), the compiler narrows the type within that branch—no explicit cast needed. Promotion also happens after a non-null assignment. Local variables promote well; fields may need explicit local copies.

dart
String? name;
if (name != null) {
  // name is promoted to non-nullable String here
  print(name.length); // safe, no ! needed
}

// promoted via is check
Object obj = 'hello';
if (obj is String) {
  print(obj.length); // smart-cast to String
}

// promoted via assignment
int? x;
x = 5;
print(x.abs()); // x is non-null after assignment

late & required

late marks a non-nullable variable that will be initialized after declaration but before first use—deferring initialization. late final initializes once (lazily if given an initializer). required marks a named parameter as mandatory. Together they integrate cleanly with null safety.

dart
class Config {
  // late: non-nullable, initialized later
  late final String value = _load();

  // late without initializer: assign before first use
  late final int computed;
  Config() {
    computed = expensive();
  }

  String _load() => 'loaded';
  int expensive() => 42;
}

// required: mandatory named parameter
void build({required String name}) {}
build(name: 'Al'); // OK
// build(); // ERROR: missing required

late Lazy Initialization

A late field with an initializer is lazy—the initializer runs on first access, not at construction. The result is cached for subsequent accesses. This is great for expensive initialization, circular references, and fields that depend on 'this' being fully constructed. late final makes it a one-time computation.

dart
class Service {
  // lazy: _expensive runs only on first access
  late final int cache = _expensive();

  int _expensive() {
    print('computing...');
    return 42;
  }
}

var s = Service();
print('created');
print(s.cache); // computing... 42
print(s.cache); // 42 (cached, no recompute)

// late fields with initializers are evaluated lazily

Was this helpful?