Flutter | เจาะลึก Enum ในภาษา Dart ครบ จบ พร้อมใช้งาน
What is Enum?
Enum คือ ชนิดของตัวแปรที่เราสามารถประกาศชุดของข้อมูลที่เราต้องการได้ ซึ่งช่วยให้ตัวแปรนั้นเข้าใจง่ายและเห็นภาพชัดเจนมากขึ้น เช่น วันในสัปดาห์ หรือ ค่าที่เป็นไปได้ของหมวดหมู่ต่างๆ
นอกจากนี้ยังทำให้โค้ดของเราอ่านง่าย ปรับแก้ไขได้สะดวก และลดโอกาสในการเกิดข้อผิดพลาดจากการใส่ค่าที่ไม่ถูกต้อง หรืออยู่นอกขอบเขตของหมวดหมู่อีกด้วย
Simple Enum
การประกาศ Enum เริ่มต้นด้วย keyword enum
ตามด้วยชื่อและค่าของข้อมูลที่เราต้องการกำหนด โดยชื่อ Enum ขึ้นต้นด้วยตัวอักษรใหญ่ตามหลัก UpperCamelCase
ส่วนค่าข้อมูลใน Enum ขึ้นต้นด้วยตัวอักษรเล็กตามหลัก camelCase
เพื่อความสอดคล้องและอ่านง่ายในโค้ด
// ENUM DECLARATION
enum Fruit { banana, apple, orange }
// USING ENUM
// If condition
if(fruit == Fruit.banana) {
print('Monkey loves me!');
}
// Switch-case
switch (fruit) {
case Fruit.banana:
print('Yellow');
case Fruit.apple:
print('Red');
case Fruit.orange:
print('Orange');
}
Properties และ methods ที่สามารถใช้งานกับ Enum ใน Dart ช่วยให้การจัดการกับ Enum สะดวกยิ่งขึ้น
Enum Properties.values
ใช้เพื่อเข้าถึงค่าทั้งหมดที่กำหนดไว้ใน Enum.index
ใช้เพื่อแสดงค่า index ของ Enum โดยเริ่มจาก 0.name
ใช้เพื่อแสดงชื่อของ Enum
// Print();
Fruit.values; // [Fruit.banana, Fruit.apple, Fruit.orange]
Fruit.banana.index; // 0
Fruit.banana; // 'Fruit.banana'
Fruit.banana.toString(); // 'Fruit.banana'
Fruit.banana.name; // 'banana'
Enum Methods.compareByIndex(value1, value2)
ใช้เพื่อเปรียบเทียบค่าของ Enum สองค่าตามตำแหน่ง index
// compareByIndex
Enum.compareByIndex(Fruit.banana, Fruit.apple); // -1
Enum.compareByIndex(Fruit.apple, Fruit.banana); // 1
Enum.compareByIndex(Fruit.banana, Fruit.banana); // 0
.compareByName(value1, value2)
ใช้เพื่อเปรียบเทียบค่าของ Enum สองค่าตามชื่อ
// compareByName
Enum.compareByName(Fruit.apple, Fruit.banana); // -1
Enum.compareByName(Fruit.banana, Fruit.apple); // 1
Enum.compareByName(Fruit.banana, Fruit.banana); // 0
Enum Sorting
การเรียงลำดับค่าของ Enum สามารถเพิ่มความสะดวกในการใช้งาน โดยเฉพาะเมื่อต้องการแสดงตัวเลือกให้กับ User
final fruits = [Fruit.orange, Fruit.banana];
fruits.sort(); // Error: not a subtype of type 'Comparable<dynamic>'
// SORTING BY COMPARE METHOD
// Pass compare methed to sort()
fruits.sort((a, b) => Enum.compareByName(a, b)); // OR
fruits.sort(Enum.compareByName);
// RESULT: Fruit.banana, Fruit.orange
// SORTING BY COMPARABLE INTERFACE
// Implement Comparable<T> interface
enum Fruit implements Comparable<Fruit> {
...
...
@override
int compareTo(Fruit other) {
return Enum.compareByName(this, other);
}
}
// Now you can sort without passing compare method
days.sort();
// RESULT: Fruit.banana, Fruit.orange
Enum extensions method สำหรับ Enum.values.byName(String name)
ใช้เพื่อค้นหาค่าใน Enum ตามชื่อที่กำหนด.asNameMap()
สร้าง map จากชื่อของค่าใน Enum
Fruit.values.byName('apple'); // Fruit.apple
// WARNING: The method .byName will throw exception,
// if the name is not found
Fruit.values.byName('grape'); // Exception!!
เนื่องจากการใช้ Enum.values.byName
สามารถทำให้เกิด Exception ได้ ถ้าค่าที่เราส่งเข้าไปไม่ถูกต้อง เพื่อหลีกเลี่ยงการเกิด exception เมื่อเรียกใช้ .byName
เราควรสร้าง extension method ที่สามารถจัดการกับ null ได้มาครอบไว้อีกชั้น แล้วเรียกใช้แทน .byName
extension EnumByNameNullable<T extends Enum> on Iterable<T> {
T? byNameOrNull(String name) {
try {
return byName(name);
} catch(_) {
return null;
}
}
}
Fruit.values.byNameOrNull('melon'); // null
Override toString()
สามารถ override toString()
ของ Enum เพื่อปรับการแสดงข้อความได้
// BEFORE: override toString()
// Print();
Fruit.values; // [Fruit.banana, Fruit.apple, Fruit.orange]
Fruit.banana; // 'Fruit.banana'
Fruit.banana.toString(); // 'Fruit.banana'
Fruit.banana.name; // 'banana'
enum Fruit {
...
...
@override
String toString() {
return name.toUpperCase();
}
}
// AFTER: override toString()
// Print();
Fruit.values; // [BANANA, APPLE, ORANGE]
Fruit.banana; // 'BANANA'
Fruit.banana.toString(); // 'BANANA'
Fruit.banana.name; // 'banana'
จากตัวอย่างข้างบนจะเห็นว่าการ override toString() ไม่ได้ทำให้ค่า
name
property เปลี่ยนแปลงไปด้วย
Enhanced Enum
ตั้งแต่ Dart 2.17 เราสามารถประกาศ Enum ที่มี property, method และ constructor รวมถึง override name property ได้เช่นกัน
โดยมีเงื่อนไขดังนี้
- property ต้องเป็น
final
เท่านั้น - constructors ต้องเป็น
const
เท่านั้น
enum Fruit {
banana('B', 60),
apple('A', 65),
orange('O', 50);
const Fruit(this.name, this.calory);
final String name
final int calory;
String get code => '$name-$calory';
int mixWith(List<Fruit> fruits) {
return fruits.fold(0, (sum, it) => sum + it.calory);
}
}
// Access Enum property
Fruit.banana.name // 'B'
Fruit.banana.calory // 60
// Access getter and method
Fruit.banana.code; // B-60
Fruit.apple.mixWith([Fruit.banana]); // 125 calory!!
// WARNING: The method Enum.values.byName
// NOT uses override name property
Fruit.apple.name; // 'A'
Fruit.values.byName('A'); // Exception!
Furit.values.byName('apple'); // Fruit.apple
Fruit.values.asNameMap(); // { banana: Fruit.banana, .., }
Fruit.values.asNameMap()['banana']; // Fruit.banana
Fruit.values.asNameMap()['B']; // null
Converting Values to Enum
สามารถเรียกผ่าน .values
เพื่อค้นหาค่า Enum จากชื่อ หรือสร้าง static method เพื่อช่วยในการค้นหา Enum จากค่าที่กำหนดได้
Fruit.values.byName('apple'); // Fruit.apple
// OR sta
enum Fruit {
banana('B', 60),
apple('A', 65),
orange('O', 50);
..
..
static Fruit? fromCalory(int cal) {
for(final value in values) {
if(value.calory == cal) {
return value;
}
}
return null;
}
}
Fruit.fromCalory(60); // Fruit.banana
Fruit.fromCalory(20); // null