{"id":82,"date":"2025-07-16T14:46:50","date_gmt":"2025-07-16T05:46:50","guid":{"rendered":"http:\/\/34.64.61.65\/?p=82"},"modified":"2025-07-16T14:51:40","modified_gmt":"2025-07-16T05:51:40","slug":"1-flutter-%ed%81%ac%eb%a1%9c%ec%8a%a4-%ed%94%8c%eb%9e%ab%ed%8f%bc-%ec%95%b1-%ea%b0%9c%eb%b0%9c-flutter%eb%a1%9c-%ed%95%9c-%eb%b0%a9%ec%97%90-%eb%81%9d%eb%82%b4%ea%b8%b0","status":"publish","type":"post","link":"https:\/\/hed-g.me\/?p=82","title":{"rendered":"1. [Flutter] \ud06c\ub85c\uc2a4 \ud50c\ub7ab\ud3fc \uc571 \uac1c\ubc1c, Flutter\ub85c \ud55c \ubc29\uc5d0 \ub05d\ub0b4\uae30!"},"content":{"rendered":"\n<h1 class=\"wp-block-heading is-style-text-subtitle is-style-text-subtitle--1\">Dart \uc5b8\uc5b4, \uc774\uac83\ub9cc \uc54c\uba74 Flutter \uac1c\ubc1c \ub05d! \ud575\uc2ec \ubb38\ubc95 \uc815\ub9ac<\/h1>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\ud83d\udcf1 \ubaa8\ubc14\uc77c \uac1c\ubc1c\uc790\ub77c\uba74 \ub204\uad6c\ub098 \ud55c \ubc88\ucbe4 \uafc8\uafd4\ubcf8 \uac83: &#8220;\ud558\ub098\uc758 \ucf54\ub4dc\ub85c Android\uc640 iOS \ubaa8\ub450\uc5d0 \ubc30\ud3ec\ud558\uae30&#8221;<br>\nFlutter\ub294 \uc774 \uafc8\uc744 \ud604\uc2e4\ub85c \ub9cc\ub4e4\uc5b4\uc8fc\ub294 \uac15\ub825\ud55c \ud504\ub808\uc784\uc6cc\ud06c\ub2e4.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfaf \uc774 \uae00\uc740 \ub204\uad6c\ub97c \uc704\ud55c \uac83\uc778\uac00\uc694?<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Android\/iOS \ub124\uc774\ud2f0\ube0c \uac1c\ubc1c\uc790<\/strong>\uc778\ub370 \ud06c\ub85c\uc2a4 \ud50c\ub7ab\ud3fc\uc73c\ub85c \uc804\ud658\ud558\uace0 \uc2f6\ub2e4<\/li>\n\n\n\n<li><strong>\uc6f9 \uac1c\ubc1c\uc790<\/strong>\uc778\ub370 \ubaa8\ubc14\uc77c \uc571 \uac1c\ubc1c\uc5d0 \ub3c4\uc804\ud574\ubcf4\uace0 \uc2f6\ub2e4<\/li>\n\n\n\n<li>Flutter\ub294 \uad00\uc2ec \uc788\ub294\ub370 <strong>Dart \uc5b8\uc5b4\uac00 \uc0dd\uc18c<\/strong>\ud574\uc11c \ub9dd\uc124\uc774\uace0 \uc788\ub2e4<\/li>\n\n\n\n<li><strong>\uc2e4\ubb34\uc5d0\uc11c \ubc14\ub85c \uc0ac\uc6a9 \uac00\ub2a5\ud55c<\/strong> \uc2e4\uc6a9\uc801\uc778 Dart \ubb38\ubc95\uc744 \ubc30\uc6b0\uace0 \uc2f6\ub2e4<\/li>\n<\/ul>\n\n\n\n<p>\uac1c\ubc1c\uc790\ub97c \uc704\ud574, <strong>Dart\uc758 \ud575\uc2ec\ub9cc \uace8\ub77c<\/strong> \uc815\ub9ac\ud588\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\ude80 Dart\ub97c \ubc30\uc6cc\uc57c \ud558\ub294 \uc774\uc720<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. Flutter\uc758 \uc2ec\uc7a5, Dart<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ \uc774 \uac04\ub2e8\ud55c \ucf54\ub4dc\uac00 Android\uc640 iOS \ubaa8\ub450\uc5d0\uc11c \ub3d9\uc791\ud569\ub2c8\ub2e4!\nvoid main() {\n  runApp(MyApp());\n}<\/code><\/pre>\n\n\n\n<p>Dart\ub294 Google\uc774 \uac1c\ubc1c\ud55c \uc5b8\uc5b4\ub85c, <strong>Flutter\uc758 \ud575\uc2ec<\/strong>\uc774\ub2e4. Java, Kotlin, Swift\uc5d0 \uc775\uc219\ud558\ub2e4\uba74 Dart \ubb38\ubc95\uc774 \uce5c\uc219\ud558\uac8c \ub290\uaef4\uc9c8 \uac83\uc774\ub2e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. \uc0dd\uc0b0\uc131\uc758 \ud601\uc2e0<\/h3>\n\n\n\n<p>\uae30\uc874\uc5d0\ub294 \uc774\ub7ac\uc8e0:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Android: Java\/Kotlin + XML<\/li>\n\n\n\n<li>iOS: Swift\/Objective-C + Storyboard<\/li>\n\n\n\n<li><strong>\uacb0\uacfc<\/strong>: \uac19\uc740 \uae30\ub2a5\uc744 \ub450 \ubc88 \uac1c\ubc1c<\/li>\n<\/ul>\n\n\n\n<p>\uc774\uc81c\ub294:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\ud558\ub098\uc758 Dart \ucf54\ub4dc<\/strong> \u2192 Android + iOS \ub3d9\uc2dc \ubc30\ud3ec<\/li>\n\n\n\n<li><strong>Hot Reload<\/strong>: \ucf54\ub4dc \uc218\uc815 \ud6c4 1\ucd08 \ub9cc\uc5d0 \uacb0\uacfc \ud655\uc778<\/li>\n\n\n\n<li><strong>\uacb0\uacfc<\/strong>: \uac1c\ubc1c \uc2dc\uac04 <strong>50% \ub2e8\ucd95<\/strong><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcda Dart \ud575\uc2ec \ubb38\ubc95 \ub9c8\uc2a4\ud130\ud558\uae30<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \ubcc0\uc218\uc640 \ub370\uc774\ud130 \ud0c0\uc785: \uc2a4\ub9c8\ud2b8\ud55c \ud0c0\uc785 \ucd94\ub860<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\ubcc0\uc218 \uc120\uc5b8\uc758 3\uac00\uc9c0 \ubc29\ubc95<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 1. var: \ud0c0\uc785 \ucd94\ub860 (\uac00\uc7a5 \uc790\uc8fc \uc0ac\uc6a9)\nvar userName = 'kimdev';        \/\/ String\uc73c\ub85c \uc790\ub3d9 \ucd94\ub860\nvar userAge = 28;               \/\/ int\ub85c \uc790\ub3d9 \ucd94\ub860\nvar isActive = true;            \/\/ bool\ub85c \uc790\ub3d9 \ucd94\ub860\n\n\/\/ 2. \uba85\uc2dc\uc801 \ud0c0\uc785 (\ud300 \ucee8\ubca4\uc158\uc774\ub098 \uba85\ud655\uc131\uc774 \ud544\uc694\ud560 \ub54c)\nString userEmail = 'kim@example.com';\nint projectCount = 5;\nbool isOnline = false;\n\n\/\/ 3. dynamic: \ubaa8\ub4e0 \ud0c0\uc785 \ud5c8\uc6a9 (\uc2e0\uc911\ud558\uac8c \uc0ac\uc6a9)\ndynamic response = {'status': 'success'};\nresponse = 'Error occurred';    \/\/ \ud0c0\uc785 \ubcc0\uacbd \uac00\ub2a5<\/code><\/pre>\n\n\n\n<p><strong>\ud83d\udca1 \uc2e4\ubb34 \ud301<\/strong>: <code>var<\/code>\ub97c \uae30\ubcf8\uc73c\ub85c \uc0ac\uc6a9\ud558\ub418, API \uc751\ub2f5\ucc98\ub7fc \ud0c0\uc785\uc774 \ubd88\ud655\uc2e4\ud55c \uacbd\uc6b0\uc5d0\ub9cc <code>dynamic<\/code>\uc744 \uc0ac\uc6a9\ud558\uc790.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">final vs const: \uc5b8\uc81c \ubb34\uc5c7\uc744 \uc0ac\uc6a9\ud560\uae4c?<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ final: \ub7f0\ud0c0\uc784\uc5d0 \ud55c \ubc88 \uc124\uc815 (API \uc751\ub2f5\uac12 \ub4f1)\nfinal String userToken = await authService.getToken();    \/\/ \u2705 OK\nfinal DateTime now = DateTime.now();            \/\/ \u2705 OK\n\n\/\/ const: \ucef4\ud30c\uc77c \ud0c0\uc784 \uc0c1\uc218 (\uc124\uc815\uac12, \uace0\uc815\uac12 \ub4f1)\nconst String appName = 'MyAwesomeApp';          \/\/ \u2705 OK\nconst int maxRetryCount = 3;                    \/\/ \u2705 OK\n\n\/\/ const DateTime now = DateTime.now();         \/\/ \u274c \uc5d0\ub7ec!<\/code><\/pre>\n\n\n\n<p><strong>\ud83d\udca1 \uc2e4\ubb34 \ud301<\/strong>: \uc124\uc815\uac12\uc740 <code>const<\/code>, API\uc5d0\uc11c \ubc1b\uc544\uc628 \uac12\uc740 <code>final<\/code>\uc744 \uc0ac\uc6a9\ud558\uc790.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">late \ud0a4\uc6cc\ub4dc: \uc9c0\uc5f0 \ucd08\uae30\ud654\uc758 \ub9c8\ubc95<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>class UserService {\n  late String token;    \/\/ \ub098\uc911\uc5d0 \ucd08\uae30\ud654\n\n  Future&lt;void&gt; initialize() async {\n    token = await getTokenFromStorage();\n    print('Token loaded: $token');\n  }\n}\n\n\/\/ \uc0ac\uc6a9\ubc95\nfinal userService = UserService();\nawait userService.initialize();    \/\/ \uc774\uc81c token \uc0ac\uc6a9 \uac00\ub2a5<\/code><\/pre>\n\n\n\n<p><strong>\ud83d\udca1 \uc2e4\ubb34 \ud301<\/strong>: SharedPreferences\ub098 SecureStorage\uc5d0\uc11c \uac12\uc744 \ubd88\ub7ec\uc62c \ub54c \uc720\uc6a9\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. \uceec\ub809\uc158: \ub370\uc774\ud130 \uad00\ub9ac\uc758 \ud575\uc2ec<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">List: \uc21c\uc11c\uac00 \uc788\ub294 \ub370\uc774\ud130<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ \uae30\ubcf8 \ub9ac\uc2a4\ud2b8\nvar fruits = &#91;'apple', 'banana', 'orange'];\nvar numbers = &lt;int&gt;&#91;1, 2, 3, 4, 5];\n\n\/\/ \uc2e4\ubb34\uc5d0\uc11c \uc790\uc8fc \uc0ac\uc6a9\ud558\ub294 \ud328\ud134\nList&lt;String&gt; todoList = &#91;];\ntodoList.add('Flutter \uacf5\ubd80\ud558\uae30');\ntodoList.addAll(&#91;'\ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791\ud558\uae30', '\ucf54\ub4dc \ub9ac\ubdf0\ud558\uae30']);\n\n\/\/ \ud568\uc218\ud615 \ud504\ub85c\uadf8\ub798\ubc0d \uc2a4\ud0c0\uc77c (\ub9e4\uc6b0 \uc720\uc6a9!)\nvar completedTasks = todoList\n    .where((task) =&gt; task.contains('\uc644\ub8cc'))\n    .map((task) =&gt; task.toUpperCase())\n    .toList();\n\nprint('\ud560 \uc77c \uac1c\uc218: ${todoList.length}');<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Map: \ud0a4-\uac12 \uc30d\uc758 \ub370\uc774\ud130<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ API \uc751\ub2f5 \ub370\uc774\ud130 \ucc98\ub9ac\uc5d0 \ud544\uc218!\nvar user = {\n  'id': 1,\n  'name': 'Kim MinJun',\n  'email': 'kim@example.com',\n  'isActive': true,\n};\n\n\/\/ \ud0c0\uc785 \uc548\uc804\ud55c \ubc29\ubc95\nMap&lt;String, dynamic&gt; apiResponse = {\n  'status': 'success',\n  'data': {\n    'users': &#91;'user1', 'user2'],\n    'count': 2,\n  }\n};\n\n\/\/ \uc548\uc804\ud55c \uc811\uadfc \ubc29\ubc95\nString status = apiResponse&#91;'status'] ?? 'unknown';\nList users = apiResponse&#91;'data']?&#91;'users'] ?? &#91;];<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Set: \uc911\ubcf5 \uc5c6\ub294 \ub370\uc774\ud130<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ \ud0dc\uadf8 \uc2dc\uc2a4\ud15c, \uce74\ud14c\uace0\ub9ac \uad00\ub9ac\uc5d0 \uc720\uc6a9\nvar tags = &lt;String&gt;{'flutter', 'dart', 'mobile'};\ntags.add('ios');\ntags.add('flutter');    \/\/ \uc911\ubcf5 \ucd94\uac00\ub418\uc9c0 \uc54a\uc74c\n\nprint(tags);    \/\/ {flutter, dart, mobile, ios}\n\n\/\/ \uc2e4\ubb34 \uc608\uc2dc: \uc0ac\uc6a9\uc790 \uad8c\ud55c \uad00\ub9ac\nSet&lt;String&gt; userPermissions = {'read', 'write'};\nbool canDelete = userPermissions.contains('delete');<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. \ud568\uc218: \uc7ac\uc0ac\uc6a9 \uac00\ub2a5\ud55c \ucf54\ub4dc \ube14\ub85d<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\uae30\ubcf8 \ud568\uc218\uc640 Arrow \ud568\uc218<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ \uc804\ud1b5\uc801\uc778 \ud568\uc218\nString getWelcomeMessage(String name) {\n  return 'Welcome, $name!';\n}\n\n\/\/ Arrow \ud568\uc218 (\ud55c \uc904\uc77c \ub54c \uae54\ub054)\nString getWelcomeMessage(String name) =&gt; 'Welcome, $name!';\n\n\/\/ \uc2e4\ubb34\uc5d0\uc11c \uc790\uc8fc \uc0ac\uc6a9\ud558\ub294 \ud328\ud134\nbool isValidEmail(String email) =&gt; email.contains('@') &amp;&amp; email.contains('.');\n\nint calculateAge(DateTime birthDate) {\n  final now = DateTime.now();\n  return now.year - birthDate.year;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">\uc120\ud0dd\uc801 \ub9e4\uac1c\ubcc0\uc218: \uc720\uc5f0\ud55c \ud568\uc218 \uc124\uacc4<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Named Parameters (\uad8c\uc7a5 \ubc29\uc2dd)\nWidget buildButton({\n  required String text,\n  VoidCallback? onPressed,\n  Color backgroundColor = Colors.blue,\n  double fontSize = 16.0,\n}) {\n  return ElevatedButton(\n    onPressed: onPressed,\n    style: ElevatedButton.styleFrom(backgroundColor: backgroundColor),\n    child: Text(text, style: TextStyle(fontSize: fontSize)),\n  );\n}\n\n\/\/ \uc0ac\uc6a9\ubc95 - \uc21c\uc11c \uc0c1\uad00\uc5c6\uc774 \uba85\ud655\ud558\uac8c!\nbuildButton(\n  text: '\ub85c\uadf8\uc778',\n  onPressed: handleLogin,\n  backgroundColor: Colors.green,\n);\n\n\/\/ Positional Parameters\nString createUrl(String domain, &#91;String? path, String? query]) {\n  var url = 'https:\/\/$domain';\n  if (path != null) url += '\/$path';\n  if (query != null) url += '?$query';\n  return url;\n}\n\n\/\/ \uc0ac\uc6a9\ubc95\ncreateUrl('api.example.com');                          \/\/ https:\/\/api.example.com\ncreateUrl('api.example.com', 'users');                 \/\/ https:\/\/api.example.com\/users\ncreateUrl('api.example.com', 'users', 'page=1');       \/\/ https:\/\/api.example.com\/users?page=1<\/code><\/pre>\n\n\n\n<p><strong>\ud83d\udca1 \uc2e4\ubb34 \ud301<\/strong>: Widget \uc0dd\uc131 \ud568\uc218\ub294 Named Parameters\ub97c, \uc720\ud2f8\ub9ac\ud2f0 \ud568\uc218\ub294 Positional Parameters\ub97c \uc0ac\uc6a9\ud558\uc790.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. \ud074\ub798\uc2a4: \uac1d\uccb4 \uc9c0\ud5a5 \ud504\ub85c\uadf8\ub798\ubc0d\uc758 \uae30\ucd08<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\ubaa8\ub378 \ud074\ub798\uc2a4: \ub370\uc774\ud130\uc758 \uad6c\uc870\ud654<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>class User {\n  final int id;\n  final String name;\n  final String email;\n  final bool isActive;\n  final DateTime? lastLoginAt;\n\n  \/\/ Constructor\n  User({\n    required this.id,\n    required this.name,\n    required this.email,\n    this.isActive = true,\n    this.lastLoginAt,\n  });\n\n  \/\/ Factory Constructor: JSON\uc5d0\uc11c \uac1d\uccb4 \uc0dd\uc131\n  factory User.fromJson(Map&lt;String, dynamic&gt; json) {\n    return User(\n      id: json&#91;'id'],\n      name: json&#91;'name'],\n      email: json&#91;'email'],\n      isActive: json&#91;'is_active'] ?? true,\n      lastLoginAt: json&#91;'last_login_at'] != null \n          ? DateTime.parse(json&#91;'last_login_at'])\n          : null,\n    );\n  }\n\n  \/\/ \uac1d\uccb4\ub97c JSON\uc73c\ub85c \ubcc0\ud658\n  Map&lt;String, dynamic&gt; toJson() {\n    return {\n      'id': id,\n      'name': name,\n      'email': email,\n      'is_active': isActive,\n      'last_login_at': lastLoginAt?.toIso8601String(),\n    };\n  }\n\n  \/\/ copyWith: \uc77c\ubd80 \ud544\ub4dc\ub9cc \ubcc0\uacbd\ud55c \uc0c8 \uac1d\uccb4 \uc0dd\uc131\n  User copyWith({\n    int? id,\n    String? name,\n    String? email,\n    bool? isActive,\n    DateTime? lastLoginAt,\n  }) {\n    return User(\n      id: id ?? this.id,\n      name: name ?? this.name,\n      email: email ?? this.email,\n      isActive: isActive ?? this.isActive,\n      lastLoginAt: lastLoginAt ?? this.lastLoginAt,\n    );\n  }\n\n  @override\n  String toString() =&gt; 'User(id: $id, name: $name, email: $email)';\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">\uc11c\ube44\uc2a4 \ud074\ub798\uc2a4: \ube44\uc988\ub2c8\uc2a4 \ub85c\uc9c1\uc758 \ubd84\ub9ac<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>class UserService {\n  final Dio _dio = Dio();\n\n  Future&lt;List&lt;User&gt;&gt; getUsers() async {\n    try {\n      final response = await _dio.get('\/api\/users');\n\n      if (response.statusCode == 200) {\n        List&lt;dynamic&gt; usersJson = response.data;\n        return usersJson.map((json) =&gt; User.fromJson(json)).toList();\n      } else {\n        throw Exception('Failed to load users');\n      }\n    } catch (e) {\n      throw Exception('Network error: $e');\n    }\n  }\n\n  Future&lt;User&gt; createUser(User user) async {\n    try {\n      final response = await _dio.post(\n        '\/api\/users',\n        data: user.toJson(),\n      );\n\n      return User.fromJson(response.data);\n    } catch (e) {\n      throw Exception('Failed to create user: $e');\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">\uc0c1\uc18d\uacfc \ucd94\uc0c1 \ud074\ub798\uc2a4: \ucf54\ub4dc\uc758 \uc7ac\uc0ac\uc6a9<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ \ucd94\uc0c1 \ud074\ub798\uc2a4: \uacf5\ud1b5 \uc778\ud130\ud398\uc774\uc2a4 \uc815\uc758\nabstract class AuthProvider {\n  Future&lt;User?&gt; signIn();\n  Future&lt;void&gt; signOut();\n  String get providerName;\n}\n\n\/\/ \uad6c\ud604 \ud074\ub798\uc2a4\nclass GoogleAuthProvider extends AuthProvider {\n  @override\n  String get providerName =&gt; 'Google';\n\n  @override\n  Future&lt;User?&gt; signIn() async {\n    \/\/ Google \ub85c\uadf8\uc778 \ub85c\uc9c1\n    final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();\n\n    if (googleUser != null) {\n      return User(\n        id: 0,\n        name: googleUser.displayName ?? '',\n        email: googleUser.email,\n      );\n    }\n    return null;\n  }\n\n  @override\n  Future&lt;void&gt; signOut() async {\n    await GoogleSignIn().signOut();\n  }\n}\n\nclass AppleAuthProvider extends AuthProvider {\n  @override\n  String get providerName =&gt; 'Apple';\n\n  @override\n  Future&lt;User?&gt; signIn() async {\n    \/\/ Apple \ub85c\uadf8\uc778 \ub85c\uc9c1\n    final credential = await SignInWithApple.getAppleIDCredential(\n      scopes: &#91;\n        AppleIDAuthorizationScopes.email,\n        AppleIDAuthorizationScopes.fullName,\n      ],\n    );\n\n    return User(\n      id: 0,\n      name: '${credential.givenName} ${credential.familyName}',\n      email: credential.email ?? '',\n    );\n  }\n\n  @override\n  Future&lt;void&gt; signOut() async {\n    \/\/ Apple \ub85c\uadf8\uc544\uc6c3\uc740 \ud074\ub77c\uc774\uc5b8\ud2b8\uc5d0\uc11c \uc9c1\uc811 \ucc98\ub9ac\n  }\n}\n\n\/\/ \uc0ac\uc6a9\ubc95\nclass AuthService {\n  Future&lt;User?&gt; signInWith(AuthProvider provider) async {\n    print('${provider.providerName}\ub85c \ub85c\uadf8\uc778 \uc911...');\n    return await provider.signIn();\n  }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">5. \ube44\ub3d9\uae30 \ud504\ub85c\uadf8\ub798\ubc0d: \ubc18\uc751\ud615 \uc571\uc758 \ud575\uc2ec<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Future: \ub2e8\uc77c \ube44\ub3d9\uae30 \uc791\uc5c5<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ \uae30\ubcf8 Future \uc0ac\uc6a9\ubc95\nFuture&lt;String&gt; fetchUserName(int userId) async {\n  \/\/ \ub124\ud2b8\uc6cc\ud06c \uc694\uccad \uc2dc\ubbac\ub808\uc774\uc158\n  await Future.delayed(Duration(seconds: 2));\n  return 'User $userId';\n}\n\n\/\/ \uc2e4\ubb34\uc5d0\uc11c \uc790\uc8fc \uc0ac\uc6a9\ud558\ub294 \ud328\ud134\nclass ApiService {\n  Future&lt;T&gt; request&lt;T&gt;(\n    String endpoint,\n    T Function(Map&lt;String, dynamic&gt;) fromJson,\n  ) async {\n    try {\n      final response = await Dio().get(endpoint);\n\n      if (response.statusCode == 200) {\n        return fromJson(response.data);\n      } else {\n        throw ApiException('HTTP ${response.statusCode}');\n      }\n    } on DioException catch (e) {\n      throw NetworkException(e.message ?? 'Network error');\n    } catch (e) {\n      throw UnknownException(e.toString());\n    }\n  }\n}\n\n\/\/ \uc5d0\ub7ec \ucc98\ub9ac\uac00 \ud3ec\ud568\ub41c \uc548\uc804\ud55c \ube44\ub3d9\uae30 \ud638\ucd9c\nFuture&lt;List&lt;User&gt;&gt; loadUsers() async {\n  try {\n    final users = await ApiService().request&lt;List&lt;User&gt;&gt;(\n      '\/users',\n      (json) =&gt; (json as List).map((item) =&gt; User.fromJson(item)).toList(),\n    );\n    return users;\n  } catch (e) {\n    print('\uc0ac\uc6a9\uc790 \ub85c\ub4dc \uc2e4\ud328: $e');\n    return &#91;];  \/\/ \ube48 \ub9ac\uc2a4\ud2b8 \ubc18\ud658\uc73c\ub85c \uc571 \ud06c\ub798\uc2dc \ubc29\uc9c0\n  }\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Stream: \uc5f0\uc18d\uc801\uc778 \ub370\uc774\ud130 \ud750\ub984<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ \uc2e4\uc2dc\uac04 \ub370\uc774\ud130 \uc2a4\ud2b8\ub9bc (\ucc44\ud305, \uc54c\ub9bc \ub4f1)\nclass ChatService {\n  final StreamController&lt;ChatMessage&gt; _messageController = \n      StreamController&lt;ChatMessage&gt;.broadcast();\n\n  Stream&lt;ChatMessage&gt; get messageStream =&gt; _messageController.stream;\n\n  void sendMessage(String text) {\n    final message = ChatMessage(\n      id: DateTime.now().millisecondsSinceEpoch.toString(),\n      text: text,\n      timestamp: DateTime.now(),\n      senderId: 'current_user',\n    );\n\n    _messageController.add(message);\n  }\n\n  void dispose() {\n    _messageController.close();\n  }\n}\n\n\/\/ Firebase Firestore \uc2e4\uc2dc\uac04 \ub9ac\uc2a4\ub108\nStream&lt;List&lt;Todo&gt;&gt; getTodosStream() {\n  return FirebaseFirestore.instance\n      .collection('todos')\n      .orderBy('createdAt', descending: true)\n      .snapshots()\n      .map((snapshot) {\n        return snapshot.docs\n            .map((doc) =&gt; Todo.fromJson(doc.data()))\n            .toList();\n      });\n}\n\n\/\/ StreamBuilder\uc640 \ud568\uaed8 \uc0ac\uc6a9\nclass TodoListWidget extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return StreamBuilder&lt;List&lt;Todo&gt;&gt;(\n      stream: getTodosStream(),\n      builder: (context, snapshot) {\n        if (snapshot.hasError) {\n          return Text('\uc5d0\ub7ec \ubc1c\uc0dd: ${snapshot.error}');\n        }\n\n        if (snapshot.connectionState == ConnectionState.waiting) {\n          return CircularProgressIndicator();\n        }\n\n        final todos = snapshot.data ?? &#91;];\n\n        return ListView.builder(\n          itemCount: todos.length,\n          itemBuilder: (context, index) {\n            return TodoItem(todo: todos&#91;index]);\n          },\n        );\n      },\n    );\n  }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">6. \ub110 \uc548\uc804\uc131(Null Safety): \uc548\uc804\ud55c \ucf54\ub4dc \uc791\uc131<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Nullable vs Non-nullable\nString name = 'Kim';        \/\/ null\uc774 \ub420 \uc218 \uc5c6\uc74c\nString? nickname;           \/\/ null\uc774 \ub420 \uc218 \uc788\uc74c\n\n\/\/ \uc548\uc804\ud55c \uc811\uadfc \ubc29\ubc95\ub4e4\nclass UserProfile {\n  String? avatarUrl;\n  String displayName;\n\n  UserProfile({this.avatarUrl, required this.displayName});\n\n  \/\/ 1. Null-aware operator (??)\n  String getDisplayName() =&gt; displayName ?? 'Anonymous';\n\n  \/\/ 2. Conditional access (?.)\n  int? getAvatarLength() =&gt; avatarUrl?.length;\n\n  \/\/ 3. Type test (is)\n  void printUserInfo() {\n    if (avatarUrl is String) {\n      print('Avatar URL: $avatarUrl');  \/\/ \uc774\uc81c String\uc73c\ub85c \ud655\uc2e0\n    }\n  }\n\n  \/\/ 4. Assertion operator (!)\n  void processAvatar() {\n    \/\/ avatarUrl\uc774 null\uc774 \uc544\ub2d8\uc744 \ud655\uc2e0\ud560 \ub54c\ub9cc \uc0ac\uc6a9\n    final url = avatarUrl!;\n    print('Processing: $url');\n  }\n\n  \/\/ 5. Late initialization\n  late String processedName;\n\n  void initializeProcessedName() {\n    processedName = displayName.toUpperCase();\n  }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">7. \uc81c\ub124\ub9ad: \ud0c0\uc785 \uc548\uc804\ud55c \uc7ac\uc0ac\uc6a9 \ucf54\ub4dc<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ API \uc751\ub2f5 \ub798\ud37c \ud074\ub798\uc2a4\nclass ApiResponse&lt;T&gt; {\n  final bool success;\n  final T? data;\n  final String? error;\n  final int statusCode;\n\n  ApiResponse({\n    required this.success,\n    this.data,\n    this.error,\n    required this.statusCode,\n  });\n\n  factory ApiResponse.success(T data, {int statusCode = 200}) {\n    return ApiResponse&lt;T&gt;(\n      success: true,\n      data: data,\n      statusCode: statusCode,\n    );\n  }\n\n  factory ApiResponse.error(String error, {int statusCode = 400}) {\n    return ApiResponse&lt;T&gt;(\n      success: false,\n      error: error,\n      statusCode: statusCode,\n    );\n  }\n}\n\n\/\/ \uc81c\ub124\ub9ad \uc11c\ube44\uc2a4 \ud074\ub798\uc2a4\nclass Repository&lt;T&gt; {\n  final String collectionName;\n  final T Function(Map&lt;String, dynamic&gt;) fromJson;\n  final Map&lt;String, dynamic&gt; Function(T) toJson;\n\n  Repository({\n    required this.collectionName,\n    required this.fromJson,\n    required this.toJson,\n  });\n\n  Future&lt;ApiResponse&lt;List&lt;T&gt;&gt;&gt; getAll() async {\n    try {\n      final response = await Dio().get('\/api\/$collectionName');\n\n      if (response.statusCode == 200) {\n        final List&lt;dynamic&gt; jsonList = response.data;\n        final List&lt;T&gt; items = jsonList.map((json) =&gt; fromJson(json)).toList();\n\n        return ApiResponse.success(items);\n      } else {\n        return ApiResponse.error('Failed to fetch $collectionName');\n      }\n    } catch (e) {\n      return ApiResponse.error(e.toString());\n    }\n  }\n\n  Future&lt;ApiResponse&lt;T&gt;&gt; create(T item) async {\n    try {\n      final response = await Dio().post(\n        '\/api\/$collectionName',\n        data: toJson(item),\n      );\n\n      if (response.statusCode == 201) {\n        return ApiResponse.success(fromJson(response.data));\n      } else {\n        return ApiResponse.error('Failed to create $collectionName');\n      }\n    } catch (e) {\n      return ApiResponse.error(e.toString());\n    }\n  }\n}\n\n\/\/ \uc0ac\uc6a9\ubc95\nfinal userRepository = Repository&lt;User&gt;(\n  collectionName: 'users',\n  fromJson: (json) =&gt; User.fromJson(json),\n  toJson: (user) =&gt; user.toJson(),\n);\n\n\/\/ \ud0c0\uc785 \uc548\uc804\ud558\uac8c \uc0ac\uc6a9\nfinal usersResponse = await userRepository.getAll();\nif (usersResponse.success) {\n  List&lt;User&gt; users = usersResponse.data!;  \/\/ \ud0c0\uc785\uc774 \ubcf4\uc7a5\ub428\n  print('\uc0ac\uc6a9\uc790 ${users.length}\uba85 \ub85c\ub4dc\ub428');\n}<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udee0\ufe0f \uc2e4\ubb34\uc5d0\uc11c \ubc14\ub85c \uc368\uba39\ub294 Dart \ud328\ud134<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \uc2f1\uae00\ud1a4 \ud328\ud134: \uc804\uc5ed \uc11c\ube44\uc2a4 \uad00\ub9ac<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>class AppConfig {\n  static AppConfig? _instance;\n  static AppConfig get instance =&gt; _instance ??= AppConfig._internal();\n\n  AppConfig._internal();\n\n  String? _apiBaseUrl;\n  String? _appVersion;\n\n  String get apiBaseUrl =&gt; _apiBaseUrl ?? 'https:\/\/api.example.com';\n  String get appVersion =&gt; _appVersion ?? '1.0.0';\n\n  Future&lt;void&gt; initialize() async {\n    \/\/ SharedPreferences\ub098 \ud658\uacbd \ubcc0\uc218\uc5d0\uc11c \uc124\uc815 \ub85c\ub4dc\n    final prefs = await SharedPreferences.getInstance();\n    _apiBaseUrl = prefs.getString('api_base_url');\n    _appVersion = prefs.getString('app_version');\n  }\n}\n\n\/\/ \uc0ac\uc6a9\ubc95\nawait AppConfig.instance.initialize();\nfinal apiUrl = AppConfig.instance.apiBaseUrl;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. Extension: \uae30\uc874 \ud074\ub798\uc2a4 \ud655\uc7a5<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ String \ud655\uc7a5\nextension StringExtensions on String {\n  bool get isValidEmail {\n    return RegExp(r'^&#91;w-.]+@(&#91;w-]+.)+&#91;w-]{2,4}\n<h3>3. Mixin: \uae30\ub2a5\uc758 \uc870\ud569<\/h3>\n<pre><code class=\"language-dart\">\/\/ \ub85c\uae45 \uae30\ub2a5 Mixin\nmixin LoggerMixin {\n  void logInfo(String message) {\n    print('&#91;INFO] ${DateTime.now()}: $message');\n  }\n\n  void logError(String message, &#91;Object? error]) {\n    print('&#91;ERROR] ${DateTime.now()}: $message');\n    if (error != null) print('Error details: $error');\n  }\n}\n\n\/\/ \uce90\uc2f1 \uae30\ub2a5 Mixin\nmixin CacheMixin&lt;T&gt; {\n  final Map&lt;String, T&gt; _cache = {};\n\n  T? getFromCache(String key) =&gt; _cache&#91;key];\n\n  void setCache(String key, T value) {\n    _cache&#91;key] = value;\n  }\n\n  void clearCache() =&gt; _cache.clear();\n}\n\n\/\/ \uc5ec\ub7ec Mixin \uc870\ud569 \uc0ac\uc6a9\nclass UserService with LoggerMixin, CacheMixin&lt;User&gt; {\n  Future&lt;User?&gt; getUser(int id) async {\n    final cacheKey = 'user_$id';\n\n    \/\/ \uce90\uc2dc \ud655\uc778\n    final cachedUser = getFromCache(cacheKey);\n    if (cachedUser != null) {\n      logInfo('User $id loaded from cache');\n      return cachedUser;\n    }\n\n    try {\n      \/\/ API \ud638\ucd9c\n      final response = await Dio().get('\/api\/users\/$id');\n      final user = User.fromJson(response.data);\n\n      \/\/ \uce90\uc2dc\uc5d0 \uc800\uc7a5\n      setCache(cacheKey, user);\n      logInfo('User $id loaded from API');\n\n      return user;\n    } catch (e) {\n      logError('Failed to load user $id', e);\n      return null;\n    }\n  }\n}<\/code><\/pre>\n<hr>\n<h2>\ud83d\udca1 \uc2e4\ubb34 \uac1c\ubc1c\uc790\ub97c \uc704\ud55c \ubca0\uc2a4\ud2b8 \ud504\ub799\ud2f0\uc2a4<\/h2>\n<h3>1. \ucf54\ub4dc \uc2a4\ud0c0\uc77c \uac00\uc774\ub4dc<\/h3>\n<pre><code class=\"language-dart\">\/\/ \u2705 \uc88b\uc740 \uc608\nclass UserRepository {\n  final ApiService _apiService;\n  final CacheManager _cacheManager;\n\n  const UserRepository({\n    required ApiService apiService,\n    required CacheManager cacheManager,\n  }) : _apiService = apiService,\n       _cacheManager = cacheManager;\n\n  Future&lt;Result&lt;User&gt;&gt; getUserById(String id) async {\n    try {\n      final user = await _apiService.getUser(id);\n      await _cacheManager.saveUser(user);\n      return Result.success(user);\n    } catch (e) {\n      return Result.error(e.toString());\n    }\n  }\n}\n\n\/\/ \u274c \ud53c\ud574\uc57c \ud560 \uc608\nclass userRepo {\n  var api;\n  var cache;\n\n  userRepo(this.api, this.cache);\n\n  getUser(id) async {\n    \/\/ \uc5d0\ub7ec \ucc98\ub9ac \uc5c6\uc74c\n    var user = await api.getUser(id);\n    cache.saveUser(user);\n    return user;\n  }\n}<\/code><\/pre>\n<h3>2. \uc5d0\ub7ec \ucc98\ub9ac \uc804\ub7b5<\/h3>\n<pre><code class=\"language-dart\">\/\/ \ucee4\uc2a4\ud140 \uc608\uc678 \ud074\ub798\uc2a4\nclass AppException implements Exception {\n  final String message;\n  final String? code;\n  final dynamic originalError;\n\n  AppException(this.message, {this.code, this.originalError});\n\n  @override\n  String toString() =&gt; 'AppException: $message${code != null ? ' ($code)' : ''}';\n}\n\nclass NetworkException extends AppException {\n  NetworkException(String message) : super(message, code: 'NETWORK_ERROR');\n}\n\nclass ValidationException extends AppException {\n  ValidationException(String message) : super(message, code: 'VALIDATION_ERROR');\n}\n\n\/\/ Result \ud328\ud134\uc73c\ub85c \uc548\uc804\ud55c \uc5d0\ub7ec \ucc98\ub9ac\nsealed class Result&lt;T&gt; {\n  const Result();\n}\n\nclass Success&lt;T&gt; extends Result&lt;T&gt; {\n  final T data;\n  const Success(this.data);\n}\n\nclass Error&lt;T&gt; extends Result&lt;T&gt; {\n  final String message;\n  final String? code;\n  const Error(this.message, {this.code});\n}\n\n\/\/ \uc0ac\uc6a9\ubc95\nFuture&lt;Result&lt;String&gt;&gt; validateEmail(String email) async {\n  if (email.isEmpty) {\n    return Error('\uc774\uba54\uc77c\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.', code: 'EMPTY_EMAIL');\n  }\n\n  if (!email.isValidEmail) {\n    return Error('\uc62c\ubc14\ub978 \uc774\uba54\uc77c \ud615\uc2dd\uc774 \uc544\ub2d9\ub2c8\ub2e4.', code: 'INVALID_EMAIL');\n  }\n\n  return Success('\uc720\ud6a8\ud55c \uc774\uba54\uc77c\uc774\ub2e4.');\n}\n\n\/\/ Switch expression\uc73c\ub85c \uae54\ub054\ud55c \ucc98\ub9ac\nString handleResult(Result&lt;String&gt; result) {\n  return switch (result) {\n    Success(data: final message) =&gt; message,\n    Error(message: final error, code: final code) =&gt; \n        'Error${code != null ? ' ($code)' : ''}: $error',\n  };\n}<\/code><\/pre>\n<h3>3. \uc131\ub2a5 \ucd5c\uc801\ud654 \ud301<\/h3>\n<pre><code class=\"language-dart\">\/\/ const \uc0dd\uc131\uc790 \ud65c\uc6a9\nclass AppColors {\n  static const primary = Color(0xFF2196F3);\n  static const secondary = Color(0xFF03DAC6);\n  static const error = Color(0xFFB00020);\n}\n\n\/\/ \ub808\uc774\uc9c0 \ub85c\ub529 \ud328\ud134\nclass AppService {\n  static ApiService? _apiService;\n  static ApiService get api =&gt; _apiService ??= ApiService();\n\n  static DatabaseService? _dbService;\n  static DatabaseService get db =&gt; _dbService ??= DatabaseService();\n}\n\n\/\/ \uba54\ubaa8\ub9ac \ud6a8\uc728\uc801\uc778 \uc2a4\ud2b8\ub9bc \uc0ac\uc6a9\nclass ChatService {\n  StreamSubscription&lt;ChatMessage&gt;? _subscription;\n\n  void startListening() {\n    _subscription = messageStream.listen(\n      (message) =&gt; handleMessage(message),\n      onError: (error) =&gt; handleError(error),\n    );\n  }\n\n  void dispose() {\n    _subscription?.cancel();\n    _subscription = null;\n  }\n}<\/code><\/pre>\n<hr>\n<h2>\ud83c\udfaf \ub2e4\uc74c \ub2e8\uacc4: Flutter \uc704\uc82f\uacfc \ud568\uaed8 \uc2e4\uc2b5\ud558\uae30<\/h2>\n<p>\uc774\uc81c Dart \uae30\ucd08\ub97c \ub9c8\uc2a4\ud130\ud588\ub2e4\uba74, Flutter \uc704\uc82f\uc73c\ub85c \uc2e4\uc81c \uc571\uc744 \ub9cc\ub4e4\uc5b4\ubcf4\uc138\uc694!<\/p>\n<h3>\ucd94\ucc9c \ud559\uc2b5 \uc21c\uc11c:<\/h3>\n<ol>\n<li><strong>StatelessWidget\uacfc StatefulWidget \uc774\ud574\ud558\uae30<\/strong><\/li>\n<li><strong>Layout \uc704\uc82f (Column, Row, Stack) \ub9c8\uc2a4\ud130\ud558\uae30<\/strong><\/li>\n<li><strong>\uc0c1\ud0dc \uad00\ub9ac (Provider, Riverpod) \uc801\uc6a9\ud558\uae30<\/strong><\/li>\n<li><strong>HTTP \ud1b5\uc2e0\uc73c\ub85c \uc2e4\uc81c API \uc5f0\ub3d9\ud558\uae30<\/strong><\/li>\n<li><strong>Firebase\ub85c \ubc31\uc5d4\ub4dc \uad6c\ucd95\ud558\uae30<\/strong><\/li>\n<\/ol>\n<h3>\uc2e4\uc2b5 \ud504\ub85c\uc81d\ud2b8 \uc544\uc774\ub514\uc5b4:<\/h3>\n<ul>\n<li><strong>Todo \uc571<\/strong>: CRUD \uae30\ub2a5\uc73c\ub85c \uae30\ubcf8\uae30 \ub2e4\uc9c0\uae30<\/li>\n<li><strong>\ub0a0\uc528 \uc571<\/strong>: API \ud1b5\uc2e0\uacfc \uc0c1\ud0dc \uad00\ub9ac \uc2e4\uc2b5<\/li>\n<li><strong>\ucc44\ud305 \uc571<\/strong>: \uc2e4\uc2dc\uac04 \ub370\uc774\ud130\uc640 Stream \ud65c\uc6a9<\/li>\n<li><strong>\uc1fc\ud551\ubab0 \uc571<\/strong>: \ubcf5\uc7a1\ud55c UI\uc640 \uc0c1\ud0dc \uad00\ub9ac \uc885\ud569<\/li>\n<\/ul>\n<hr>\n<h2>\ud83d\udcda \ub9c8\ubb34\ub9ac: \uc131\uc7a5\ud558\ub294 \uac1c\ubc1c\uc790\ub97c \uc704\ud55c \uc870\uc5b8<\/h2>\n<blockquote><p>\"\uc644\ubcbd\ud55c \ucf54\ub4dc\ub97c \ucc98\uc74c\ubd80\ud130 \uc791\uc131\ud558\ub824 \ud558\uc9c0 \ub9c8\uc138\uc694. \ub3d9\uc791\ud558\ub294 \ucf54\ub4dc\ub97c \uba3c\uc800 \ub9cc\ub4e4\uace0, \uc810\ucc28 \uac1c\uc120\ud574 \ub098\uac00\uc138\uc694.\"<\/p><\/blockquote>\n<p>\uac1c\ubc1c\uc790\uc5d0\uac8c Dart\ub294 <strong>\uac15\ub825\ud55c \ub3c4\uad6c<\/strong>\ub2e4. \ud558\uc9c0\ub9cc \uae30\uc5b5\ud558\uc790:<\/p>\n<h3>\u2705 \uc2e4\ubb34\uc5d0\uc11c \uc911\uc694\ud55c \uac83\ub4e4:<\/h3>\n<ul>\n<li><strong>\uac00\ub3c5\uc131<\/strong>: \ub3d9\ub8cc\uac00 \uc774\ud574\ud558\uae30 \uc26c\uc6b4 \ucf54\ub4dc<\/li>\n<li><strong>\uc548\uc815\uc131<\/strong>: \uc5d0\ub7ec \ucc98\ub9ac\uc640 \ub110 \uc548\uc804\uc131<\/li>\n<li><strong>\ud655\uc7a5\uc131<\/strong>: \uae30\ub2a5 \ucd94\uac00\uac00 \uc26c\uc6b4 \uad6c\uc870<\/li>\n<li><strong>\uc131\ub2a5<\/strong>: \ubd88\ud544\uc694\ud55c \uc5f0\uc0b0 \ucd5c\uc18c\ud654<\/li>\n<\/ul>\n<h3>\ud83d\ude80 \uacc4\uc18d \uc131\uc7a5\ud558\uae30 \uc704\ud55c \ud301:<\/h3>\n<ol>\n<li><strong>\uacf5\uc2dd \ubb38\uc11c\ub97c \uce5c\uad6c\ub85c \ub9cc\ub4dc\uc138\uc694<\/strong>: <a href=\"https:\/\/dart.dev\">dart.dev<\/a><\/li>\n<li><strong>\ucf54\ub4dc \ub9ac\ubdf0\ub97c \uc801\uadf9 \ud65c\uc6a9\ud558\uc790<\/strong>: \ub2e4\ub978 \uc0ac\ub78c\uc758 \ud53c\ub4dc\ubc31\uc774 \uac00\uc7a5 \ube60\ub978 \uc131\uc7a5 \ubc29\ubc95<\/li>\n<li><strong>\uc624\ud508\uc18c\uc2a4\uc5d0 \uae30\uc5ec\ud574\ubcf4\uc138\uc694<\/strong>: \uc2e4\uc81c \ud504\ub85c\uc81d\ud2b8 \uacbd\ud5d8\uc774 \ucd5c\uace0\uc758 \uc2a4\uc2b9<\/li>\n<li><strong>\ucee4\ubba4\ub2c8\ud2f0\uc5d0 \ucc38\uc5ec\ud558\uc790<\/strong>: <a href=\"https:\/\/flutter-ko.dev\">Flutter Korea<\/a>, Stack Overflow \ub4f1<\/li>\n<\/ol>\n<p>\ub2e4\uc74c \uae00\uc5d0\uc11c\ub294 <strong>\"Flutter \uc704\uc82f \uc644\uc804 \uc815\ubcf5\"<\/strong>\uc73c\ub85c \ub3cc\uc544\uc62c \uc608\uc815\uc774\ub2e4! <\/p>\n<hr>\n<h2>\ud83d\udd17 \uad00\ub828 \uc790\ub8cc<\/h2>\n<ul>\n<li><a href=\"https:\/\/dart.dev\/guides\">Dart \uacf5\uc2dd \ubb38\uc11c<\/a><\/li>\n<li><a href=\"https:\/\/flutter.dev\/docs\">Flutter \uacf5\uc2dd \ubb38\uc11c<\/a><\/li>\n<li><a href=\"https:\/\/dart.dev\/guides\/language\/effective-dart\">Effective Dart \uac00\uc774\ub4dc<\/a><\/li>\n<li><a href=\"https:\/\/dartpad.dev\">DartPad<\/a> - \ube0c\ub77c\uc6b0\uc800\uc5d0\uc11c Dart \ucf54\ub4dc \uc2e4\uc2b5<\/li>\n<\/ul>\n<p><strong>Happy Coding! \ud83c\udf89<\/strong><\/p>\n<hr>\n<p><em>\uc774 \uae00\uc774 \ub3c4\uc6c0\uc774 \ub418\uc5c8\ub2e4\uba74 \uc88b\uc544\uc694\uc640 \ub313\uae00\uc744 \ub0a8\uaca8\uc8fc\uc138\uc694. \ud53c\ub4dc\ubc31\uc740 \uc5b8\uc81c\ub098 \ud658\uc601\uc785\ub2c8\ub2e4! \ud83d\udcaa<\/em><\/p>\n).hasMatch(this);\n  }\n\n  String get toTitleCase {\n    return split(' ')\n        .map((word) =&gt; word.isEmpty \n            ? word \n            : word&#91;0].toUpperCase() + word.substring(1).toLowerCase())\n        .join(' ');\n  }\n\n  String truncate(int maxLength) {\n    return length &gt; maxLength \n        ? '${substring(0, maxLength)}...' \n        : this;\n  }\n}\n\n\/\/ DateTime \ud655\uc7a5\nextension DateTimeExtensions on DateTime {\n  String get timeAgo {\n    final now = DateTime.now();\n    final difference = now.difference(this);\n\n    if (difference.inDays &gt; 0) {\n      return '${difference.inDays}\uc77c \uc804';\n    } else if (difference.inHours &gt; 0) {\n      return '${difference.inHours}\uc2dc\uac04 \uc804';\n    } else if (difference.inMinutes &gt; 0) {\n      return '${difference.inMinutes}\ubd84 \uc804';\n    } else {\n      return '\ubc29\uae08 \uc804';\n    }\n  }\n\n  bool get isToday {\n    final now = DateTime.now();\n    return year == now.year &amp;&amp; month == now.month &amp;&amp; day == now.day;\n  }\n}\n\n\/\/ \uc0ac\uc6a9\ubc95\n'kim@example.com'.isValidEmail;                    \/\/ true\n'hello world'.toTitleCase;                         \/\/ 'Hello World'\n'\uae34 \ud14d\uc2a4\ud2b8\uc785\ub2c8\ub2e4'.truncate(5);                         \/\/ '\uae34 \ud14d\uc2a4\ud2b8\uc785...'\n\nDateTime.now().subtract(Duration(hours: 2)).timeAgo;  \/\/ '2\uc2dc\uac04 \uc804'\nDateTime.now().isToday;                               \/\/ true<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. Mixin: \uae30\ub2a5\uc758 \uc870\ud569<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udca1 \uc2e4\ubb34 \uac1c\ubc1c\uc790\ub97c \uc704\ud55c \ubca0\uc2a4\ud2b8 \ud504\ub799\ud2f0\uc2a4<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \ucf54\ub4dc \uc2a4\ud0c0\uc77c \uac00\uc774\ub4dc<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. \uc5d0\ub7ec \ucc98\ub9ac \uc804\ub7b5<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. \uc131\ub2a5 \ucd5c\uc801\ud654 \ud301<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfaf \ub2e4\uc74c \ub2e8\uacc4: Flutter \uc704\uc82f\uacfc \ud568\uaed8 \uc2e4\uc2b5\ud558\uae30<\/h2>\n\n\n\n<p>\uc774\uc81c Dart \uae30\ucd08\ub97c \ub9c8\uc2a4\ud130\ud588\ub2e4\uba74, Flutter \uc704\uc82f\uc73c\ub85c \uc2e4\uc81c \uc571\uc744 \ub9cc\ub4e4\uc5b4\ubcf4\uc138\uc694!<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\ucd94\ucc9c \ud559\uc2b5 \uc21c\uc11c:<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>StatelessWidget\uacfc StatefulWidget \uc774\ud574\ud558\uae30<\/strong><\/li>\n\n\n\n<li><strong>Layout \uc704\uc82f (Column, Row, Stack) \ub9c8\uc2a4\ud130\ud558\uae30<\/strong><\/li>\n\n\n\n<li><strong>\uc0c1\ud0dc \uad00\ub9ac (Provider, Riverpod) \uc801\uc6a9\ud558\uae30<\/strong><\/li>\n\n\n\n<li><strong>HTTP \ud1b5\uc2e0\uc73c\ub85c \uc2e4\uc81c API \uc5f0\ub3d9\ud558\uae30<\/strong><\/li>\n\n\n\n<li><strong>Firebase\ub85c \ubc31\uc5d4\ub4dc \uad6c\ucd95\ud558\uae30<\/strong><\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\uc2e4\uc2b5 \ud504\ub85c\uc81d\ud2b8 \uc544\uc774\ub514\uc5b4:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Todo \uc571<\/strong>: CRUD \uae30\ub2a5\uc73c\ub85c \uae30\ubcf8\uae30 \ub2e4\uc9c0\uae30<\/li>\n\n\n\n<li><strong>\ub0a0\uc528 \uc571<\/strong>: API \ud1b5\uc2e0\uacfc \uc0c1\ud0dc \uad00\ub9ac \uc2e4\uc2b5<\/li>\n\n\n\n<li><strong>\ucc44\ud305 \uc571<\/strong>: \uc2e4\uc2dc\uac04 \ub370\uc774\ud130\uc640 Stream \ud65c\uc6a9<\/li>\n\n\n\n<li><strong>\uc1fc\ud551\ubab0 \uc571<\/strong>: \ubcf5\uc7a1\ud55c UI\uc640 \uc0c1\ud0dc \uad00\ub9ac \uc885\ud569<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcda \ub9c8\ubb34\ub9ac: \uc131\uc7a5\ud558\ub294 \uac1c\ubc1c\uc790\ub97c \uc704\ud55c \uc870\uc5b8<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>&#8220;\uc644\ubcbd\ud55c \ucf54\ub4dc\ub97c \ucc98\uc74c\ubd80\ud130 \uc791\uc131\ud558\ub824 \ud558\uc9c0 \ub9c8\uc138\uc694. \ub3d9\uc791\ud558\ub294 \ucf54\ub4dc\ub97c \uba3c\uc800 \ub9cc\ub4e4\uace0, \uc810\ucc28 \uac1c\uc120\ud574 \ub098\uac00\uc138\uc694.&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p>\uac1c\ubc1c\uc790\uc5d0\uac8c Dart\ub294 <strong>\uac15\ub825\ud55c \ub3c4\uad6c<\/strong>\ub2e4. \ud558\uc9c0\ub9cc \uae30\uc5b5\ud558\uc790:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 \uc2e4\ubb34\uc5d0\uc11c \uc911\uc694\ud55c \uac83\ub4e4:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\uac00\ub3c5\uc131<\/strong>: \ub3d9\ub8cc\uac00 \uc774\ud574\ud558\uae30 \uc26c\uc6b4 \ucf54\ub4dc<\/li>\n\n\n\n<li><strong>\uc548\uc815\uc131<\/strong>: \uc5d0\ub7ec \ucc98\ub9ac\uc640 \ub110 \uc548\uc804\uc131<\/li>\n\n\n\n<li><strong>\ud655\uc7a5\uc131<\/strong>: \uae30\ub2a5 \ucd94\uac00\uac00 \uc26c\uc6b4 \uad6c\uc870<\/li>\n\n\n\n<li><strong>\uc131\ub2a5<\/strong>: \ubd88\ud544\uc694\ud55c \uc5f0\uc0b0 \ucd5c\uc18c\ud654<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\ude80 \uacc4\uc18d \uc131\uc7a5\ud558\uae30 \uc704\ud55c \ud301:<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\uacf5\uc2dd \ubb38\uc11c\ub97c \uce5c\uad6c\ub85c \ub9cc\ub4dc\uc138\uc694<\/strong>: <a href=\"https:\/\/dart.dev\">dart.dev<\/a><\/li>\n\n\n\n<li><strong>\ucf54\ub4dc \ub9ac\ubdf0\ub97c \uc801\uadf9 \ud65c\uc6a9\ud558\uc790<\/strong>: \ub2e4\ub978 \uc0ac\ub78c\uc758 \ud53c\ub4dc\ubc31\uc774 \uac00\uc7a5 \ube60\ub978 \uc131\uc7a5 \ubc29\ubc95<\/li>\n\n\n\n<li><strong>\uc624\ud508\uc18c\uc2a4\uc5d0 \uae30\uc5ec\ud574\ubcf4\uc138\uc694<\/strong>: \uc2e4\uc81c \ud504\ub85c\uc81d\ud2b8 \uacbd\ud5d8\uc774 \ucd5c\uace0\uc758 \uc2a4\uc2b9<\/li>\n\n\n\n<li><strong>\ucee4\ubba4\ub2c8\ud2f0\uc5d0 \ucc38\uc5ec\ud558\uc790<\/strong>: <a href=\"https:\/\/flutter-ko.dev\">Flutter Korea<\/a>, Stack Overflow \ub4f1<\/li>\n<\/ol>\n\n\n\n<p>\ub2e4\uc74c \uae00\uc5d0\uc11c\ub294 <strong>&#8220;Flutter \uc704\uc82f \uc644\uc804 \uc815\ubcf5&#8221;<\/strong>\uc73c\ub85c \ub3cc\uc544\uc62c \uc608\uc815\uc774\ub2e4! <\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd17 \uad00\ub828 \uc790\ub8cc<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/dart.dev\/guides\">Dart \uacf5\uc2dd \ubb38\uc11c<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/flutter.dev\/docs\">Flutter \uacf5\uc2dd \ubb38\uc11c<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/dart.dev\/guides\/language\/effective-dart\">Effective Dart \uac00\uc774\ub4dc<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/dartpad.dev\">DartPad<\/a> &#8211; \ube0c\ub77c\uc6b0\uc800\uc5d0\uc11c Dart \ucf54\ub4dc \uc2e4\uc2b5<\/li>\n<\/ul>\n\n\n\n<p><strong>Happy Coding! \ud83c\udf89<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><em>\uc774 \uae00\uc774 \ub3c4\uc6c0\uc774 \ub418\uc5c8\ub2e4\uba74 \uc88b\uc544\uc694\uc640 \ub313\uae00\uc744 \ub0a8\uaca8\uc8fc\uc138\uc694. \ud53c\ub4dc\ubc31\uc740 \uc5b8\uc81c\ub098 \ud658\uc601\uc785\ub2c8\ub2e4! \ud83d\udcaa<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dart \uc5b8\uc5b4, \uc774\uac83\ub9cc \uc54c\uba74 Flutter \uac1c\ubc1c \ub05d! \ud575\uc2ec \ubb38\ubc95 \uc815\ub9ac \ud83d\udcf1 \ubaa8\ubc14\uc77c \uac1c\ubc1c\uc790\ub77c\uba74 \ub204\uad6c\ub098 \ud55c \ubc88\ucbe4 \uafc8\uafd4\ubcf8 \uac83: &#8220;\ud558\ub098\uc758 \ucf54\ub4dc\ub85c Android\uc640 iOS \ubaa8\ub450\uc5d0 \ubc30\ud3ec\ud558\uae30&#8221; Flutter\ub294 \uc774 \uafc8\uc744 \ud604\uc2e4\ub85c \ub9cc\ub4e4\uc5b4\uc8fc\ub294 \uac15\ub825\ud55c \ud504\ub808\uc784\uc6cc\ud06c\ub2e4. \ud83c\udfaf \uc774 \uae00\uc740 \ub204\uad6c\ub97c \uc704\ud55c \uac83\uc778\uac00\uc694? \uac1c\ubc1c\uc790\ub97c \uc704\ud574, Dart\uc758 \ud575\uc2ec\ub9cc \uace8\ub77c \uc815\ub9ac\ud588\ub2e4. \ud83d\ude80 Dart\ub97c \ubc30\uc6cc\uc57c \ud558\ub294 \uc774\uc720 1. Flutter\uc758 \uc2ec\uc7a5, Dart Dart\ub294 Google\uc774 \uac1c\ubc1c\ud55c [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[43],"tags":[114,110,78,113,111,79,112,106],"class_list":["post-82","post","type-post","status-publish","format-standard","hentry","category-flutter","tag-android","tag-dart","tag-flutter","tag-ios","tag-111","tag-79","tag-112","tag-106"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/posts\/82","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hed-g.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=82"}],"version-history":[{"count":2,"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/posts\/82\/revisions"}],"predecessor-version":[{"id":104,"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/posts\/82\/revisions\/104"}],"wp:attachment":[{"href":"https:\/\/hed-g.me\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=82"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hed-g.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=82"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hed-g.me\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=82"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}