ลด Boilerplate Code ด้วย Freezed Package ใน Flutter
ในโลกของการพัฒนาแอปพลิเคชัน Flutter การจัดการข้อมูลถือเป็นหัวใจสำคัญ และบ่อยครั้งที่เราต้องสร้าง Data Classes เพื่อเก็บข้อมูลเหล่านี้ แต่การสร้าง Data Classes ด้วยตนเองนั้นมักจะมาพร้อมกับ Boilerplate Code จำนวนมาก เช่น การเขียน Constructor, `copyWith`, `toJson`, `fromJson`, `==` operator, และ `hashCode` property ด้วย Freezed Package ปัญหาเหล่านี้จะหมดไป เพราะ Freezed จะช่วย Generate โค้ดเหล่านี้ให้เราอัตโนมัติ ทำให้เราสามารถโฟกัสกับการพัฒนา Logic ของแอปพลิเคชันได้มากขึ้น
บทความนี้จะแนะนำวิธีการใช้งาน Freezed Package เพื่อ Generate Data Classes ใน Flutter แบบ Step-by-Step พร้อมทั้งยกตัวอย่างการใช้งานจริงและข้อผิดพลาดที่พบบ่อย เพื่อให้คุณสามารถนำไปประยุกต์ใช้กับโปรเจกต์ของคุณได้อย่างมีประสิทธิภาพ
ทำไมต้องใช้ Freezed?
ก่อนที่เราจะเริ่มใช้งาน Freezed เรามาดูกันก่อนว่าทำไมเราถึงควรใช้ Freezed ในโปรเจกต์ Flutter ของเรา:
- ลด Boilerplate Code: Freezed ช่วยลดปริมาณโค้ดที่เราต้องเขียนด้วยตนเองอย่างมาก ทำให้โค้ดของเราสะอาดและอ่านง่ายขึ้น
- เพิ่มความถูกต้อง: การ Generate โค้ดอัตโนมัติช่วยลดโอกาสในการเกิดข้อผิดพลาดจากการเขียนโค้ดซ้ำๆ
- Immutable Data: Freezed สร้าง Data Classes ที่เป็น Immutable โดยค่าเริ่มต้น ทำให้ง่ายต่อการจัดการ State และป้องกันการเปลี่ยนแปลงข้อมูลโดยไม่ตั้งใจ
- Pattern Matching: Freezed รองรับ Pattern Matching ทำให้เราสามารถเขียนโค้ดที่จัดการกับ State ต่างๆ ได้อย่างสวยงามและกระชับ
- copyWith: Freezed Generate ฟังก์ชัน `copyWith` ให้เราโดยอัตโนมัติ ทำให้ง่ายต่อการสร้าง Instance ใหม่ของ Data Class โดยมีการเปลี่ยนแปลงบาง Property
- toJson/fromJson: Freezed รองรับการ Generate ฟังก์ชัน `toJson` และ `fromJson` ทำให้ง่ายต่อการ Serialize และ Deserialize ข้อมูล
การติดตั้ง Freezed Package
ในการเริ่มต้นใช้งาน Freezed เราต้องติดตั้ง Packages ที่จำเป็นดังนี้:
dependencies:
flutter:
sdk: flutter
freezed_annotation: ^2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.0.0
freezed: ^2.0.0
json_serializable: ^6.0.0
เพิ่ม Dependencies เหล่านี้ลงในไฟล์ `pubspec.yaml` ของโปรเจกต์ Flutter ของคุณ จากนั้นรันคำสั่ง `flutter pub get` เพื่อติดตั้ง Dependencies
คำอธิบาย Dependencies:
- freezed_annotation: Package ที่ใช้ Annotation `@freezed` เพื่อบอกว่า Class นี้ควรถูก Generate โดย Freezed
- build_runner: Package ที่ใช้ในการ Generate โค้ด
- freezed: Package ที่ใช้ในการ Generate โค้ดจาก Annotation `@freezed`
- json_serializable: Package ที่ใช้ในการ Generate ฟังก์ชัน `toJson` และ `fromJson`
การใช้งาน @freezed
หลังจากติดตั้ง Packages ที่จำเป็นแล้ว เรามาลองสร้าง Data Class ด้วย Freezed กันเลยดีกว่า สมมติว่าเราต้องการสร้าง Data Class ที่เก็บข้อมูลผู้ใช้ (User) โดยมี Properties คือ `id`, `name`, และ `email`
- สร้างไฟล์ `user.dart`: สร้างไฟล์ `user.dart` ใน Directory ที่เหมาะสม (เช่น `lib/models`)
- เขียนโค้ด Data Class: เขียนโค้ด Data Class โดยใช้ Annotation `@freezed` ดังนี้:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
คำอธิบายโค้ด:
- `part 'user.freezed.dart';`: บรรทัดนี้บอกให้ Dart Compiler รู้ว่าโค้ดที่ Generate จะถูกเก็บไว้ในไฟล์ `user.freezed.dart`
- `part 'user.g.dart';`: บรรทัดนี้บอกให้ Dart Compiler รู้ว่าโค้ดที่ Generate สำหรับ JSON Serialization จะถูกเก็บไว้ในไฟล์ `user.g.dart`
- `@freezed`: Annotation นี้บอกให้ Freezed Package ทำการ Generate โค้ดสำหรับ Class นี้
- `class User with _$User`: บรรทัดนี้บอกว่า Class `User` จะใช้ Mixin `_$User` ซึ่งถูก Generate โดย Freezed
- `const factory User(...) = _User;`: Constructor นี้เป็น Factory Constructor ที่ใช้สร้าง Instance ของ Data Class และกำหนดให้เป็น `const` เพื่อให้ Instance เป็น Immutable
- `factory User.fromJson(...) => _$UserFromJson(json);`: Constructor นี้ใช้สำหรับ Deserialize ข้อมูลจาก JSON
ข้อสังเกต: อย่าลืมใส่ `required` Keyword หน้า Properties เพื่อบังคับให้ Properties เหล่านั้นต้องมีค่าเสมอ
- Generate โค้ด: รันคำสั่ง `flutter pub run build_runner build` เพื่อ Generate โค้ด
หลังจากรันคำสั่งนี้สำเร็จ จะมีไฟล์ `user.freezed.dart` และ `user.g.dart` ถูกสร้างขึ้นใน Directory เดียวกับ `user.dart` ไฟล์เหล่านี้มีโค้ดที่ถูก Generate โดย Freezed และ `json_serializable` ซึ่งรวมถึง Constructor, `copyWith`, `toJson`, `fromJson`, `==` operator, และ `hashCode` property
การใช้งาน copyWith
เมื่อ Data Class ของเราเป็น Immutable การแก้ไขข้อมูลจึงต้องทำโดยการสร้าง Instance ใหม่ที่มีการเปลี่ยนแปลงบาง Property ฟังก์ชัน `copyWith` ที่ถูก Generate โดย Freezed ช่วยให้กระบวนการนี้ง่ายขึ้นมาก
final user = User(id: '1', name: 'John Doe', email: 'john.doe@example.com');
final updatedUser = user.copyWith(name: 'Jane Doe');
print(user.name); // Output: John Doe
print(updatedUser.name); // Output: Jane Doe
จากตัวอย่างข้างต้น เราสร้าง Instance ของ `User` และใช้ฟังก์ชัน `copyWith` เพื่อสร้าง Instance ใหม่ที่มีชื่อเป็น "Jane Doe" สังเกตว่า Instance เดิม (`user`) ไม่มีการเปลี่ยนแปลง
การใช้งาน toJson และ fromJson
Freezed รองรับการ Generate ฟังก์ชัน `toJson` และ `fromJson` ทำให้เราสามารถ Serialize และ Deserialize ข้อมูลได้อย่างง่ายดาย
final user = User(id: '1', name: 'John Doe', email: 'john.doe@example.com');
final json = user.toJson();
print(json); // Output: {id: 1, name: John Doe, email: john.doe@example.com}
final userFromJson = User.fromJson(json);
print(userFromJson); // Output: Instance of 'User'
จากตัวอย่างข้างต้น เราใช้ฟังก์ชัน `toJson` เพื่อแปลง Instance ของ `User` เป็น JSON Object และใช้ฟังก์ชัน `fromJson` เพื่อสร้าง Instance ของ `User` จาก JSON Object
กรณีใช้งานจริงและข้อผิดพลาดที่พบบ่อย
กรณีใช้งานจริง:
- จัดการ State ในแอปพลิเคชัน: ใช้ Freezed เพื่อสร้าง Data Classes ที่เก็บ State ของแอปพลิเคชัน
- รับส่งข้อมูลกับ API: ใช้ Freezed ร่วมกับ `json_serializable` เพื่อ Serialize และ Deserialize ข้อมูลที่รับส่งกับ API
- สร้าง Models สำหรับ Database: ใช้ Freezed เพื่อสร้าง Models ที่ใช้ในการจัดเก็บข้อมูลใน Database
ข้อผิดพลาดที่พบบ่อย:
- ลืม Import `freezed_annotation`: อย่าลืม Import `freezed_annotation` ในไฟล์ Data Class
- ลืมรัน `build_runner`: หลังจากแก้ไข Data Class แล้ว อย่าลืมรัน `flutter pub run build_runner build` เพื่อ Generate โค้ดใหม่
- Error เกี่ยวกับ Part Directive: ตรวจสอบให้แน่ใจว่า Part Directive (`part 'user.freezed.dart';` และ `part 'user.g.dart';`) ถูกต้องและอยู่ในตำแหน่งที่ถูกต้อง
- ปัญหาเรื่อง Null Safety: หากใช้ Flutter เวอร์ชันที่รองรับ Null Safety ตรวจสอบให้แน่ใจว่า Properties ทั้งหมดมี Type ที่ถูกต้องและจัดการกับค่า Null ได้อย่างถูกต้อง
สรุป
Freezed Package เป็นเครื่องมือที่มีประโยชน์อย่างมากในการลด Boilerplate Code และเพิ่มประสิทธิภาพในการพัฒนาแอปพลิเคชัน Flutter ด้วยการ Generate Data Classes อัตโนมัติ ทำให้เราสามารถโฟกัสกับการพัฒนา Logic ของแอปพลิเคชันได้มากขึ้น หวังว่าบทความนี้จะเป็นประโยชน์และช่วยให้คุณสามารถนำ Freezed ไปประยุกต์ใช้กับโปรเจกต์ของคุณได้อย่างมีประสิทธิภาพ