ถ้า iOS Developer อยากเขียน Flutter ต้องทำยังไง?
Flutter เป็น UI Framework ที่ไว้สำหรับเขียนครั้งเดียวเพื่อลงหลายๆ แพลตฟอร์ม แต่คำกล่าวที่ว่า “เขียนครั้งเดียว” แล้วลงได้หมดนั้นอาจจะเว่อร์ไปสักหน่อย เพราะสุดท้ายก็ต้องมาปรับให้เหมาะกับแต่ละแพลตฟอร์มอีกรอบอยู่ดี เช่น เราเขียนเพื่อเรียก GPS บนแอปมือถือ แต่เราจะไม่สามารถใช้ฟังก์ชันนี้บน Windows หรือ macOS ได้ ฉะนั้นแล้วโค้ดจะไม่ได้เขียนแค่ครั้งเดียวแล้วนะครับ อย่างไรก็ดีการที่แชร์กันได้อย่างน้อยกว่า 90% นั้นนับว่ายอมเยี่ยมแล้ว
ตัวผมเองเริ่มต้นเขียน iOS ตั้งแต่สมัย iPhone 3GS และในช่วง 1.8 ปีที่ผ่านมาได้เรียนรู้เกี่ยวกับ Flutter เพิ่มเติมเพราะมีโปรเจคที่ต้องใช้ ซึ่ง Dart เป็นภาษาและ Framework ที่ผมชอบ อ่านง่าย ทั้งยังโชคดีที่ Flutter Team ทำ Tutorial ไว้ดีมาก ช่วยให้ผมสามารถเริ่มต้นง่ายๆ
ตอนแรกที่ได้เริ่มเขียน Dart ก็รู้สึกว่าภาษาแปลกดี มีทั้งอันที่ชอบและไม่ค่อยชอบ ย้อนไปตอนเริ่มเขียนครั้งแรก ผมพยายามหาว่ามีใครไหมนะที่มาจากสาย iOS เหมือนกันแล้วทดเหมือนโน้ตข้อสอบว่า Swift ใช้แบบนี้ Dart ใช้ยังไง พอลองหาแล้วปรากฏว่าไม่เจอเลย จึงต้องมาเรียนรู้เองระดับหนึ่งเหมือนกัน ทั้งเรื่อง Syntax ต่างๆ ผ่านไปสักพักก็คิดว่าเราเนี่ยแหละที่ควรต้องเขียนขึ้นมา เพราะเราเองรู้ทั้ง 2 ภาษา ดังนั้นน่าจะมีประโยชน์ต่อเพื่อนๆ iOS เหมือนกัน หลักๆ ผมนำมาจาก Doc ของ Swift แล้วแปลเป็น Dart นะครับ มาเริ่มด้วยเรื่องพื้นฐานสุดๆ อย่าง Variable กัน
การประกาศตัวแปร Constants and Variables
Swift
let name = “Swift”var lastname = “Apple”
Dart
final name = "Dart";
const nickName = "Flutter";var lastname = "Google";
สำหรับตัวแปรที่เปลี่ยนค่าไม่ได้ Dart จะใช้คำว่า Final ในขณะที่ Swift ใช้ Let ส่วนตัวแปรที่เปลี่ยนค่าได้ ทั้งคู่จะใช้คำว่า Var เหมือนกัน
ในภาษา Dart จะมีประกาศตัวแปรแบบ Immutable Objects อยู่ 2 แบบ คือ final
กับ const
2 แบบนี้ต่างกันยังไง?
- Final คุณไม่รู้ว่าค่าสุดท้ายจะเป็นค่าอะไรขณะที่ Compile เพราะค่าจะถูกใส่เมื่อ Run Time ยกตัวอย่างเช่น การดึงข้อมูลจาก API
- Const คุณรู้ว่าคืออะไรตั้งแต่ตอน Compile เช่น API Path หรือชื่อตัวแปรที่จะใช้
Note: อย่าลืมนะครับ Dart ต้องการ Semicolon ตอนท้าย ไม่เหมือนกับ Swift
การประกาศชื่อ
Swift
private var name: String = "Swift"
let isLoggedIn = false
Dart
String _name = "Swift"; // It's mean private
var _name = "Swift"; // has the same meaning as above but you can omit declare Stringfinal isLoggedIn = false;
ใน Swift เราสามารถเติมคำว่า private
, internal
, public
, protected
, fileprivate
ในหน้า Class, Function หรือ Variable ได้
ใน Dart การประกาศแบบ Private จะใช้ _
นำหน้าชื่อที่จะใช้ ถ้าไม่มีจะนับว่าเป็น Public เราสามารถประกาศบอก Type ได้เหมือนกับ Swift เจาะจงไปเลยว่าเป็น String หรือ Int
Tuples
Swift
let http404Error = (404, “Not Found”)
ส่วน Dart ไม่มี Tuples จะใช้ Object แทน
Optionals
Swift
var serverResponseCode: Int? = 404
Dart ก่อน 2.12 จะยังไม่มีเรื่อง Optional ซึ่งจะใช้กับ Flutter 2.0 เป็นต้นไป ส่วน Dart 2.12 จะประกาศเหมือนด้านล่างครับ
Int? serverResponseCode = 404;
If Statements and Forced Unwrapping
Swift
var name: String? = "Swift"
if let unwrapName = name {
print(unwrapName)} else {
print("name is null")}// Or you can use Guard with let
var name: String? = "Swift"
guard let unwrapName = name else {
print("name is null")return
}print(unwrapName)
Dart ไม่มีการ Unwrap Optional จึงใช้วิธีเช็คว่าค่าเป็น Null รึเปล่า ก่อนที่จะนำไปใช้แบบ Java
String? name = "Dart";
if (name != null) {
print(name);} else {
print("name is null");}
Error Handling
Swift
func testAge(age: Int) throws {
if age < 0 {
throw "Age can't be negative"
} // do something}do { try testAge(age: -2)} catch {// an error was thrown}
Dart
void testAge(int age) { // void is optional, no need to declare
if (age < 0) {
throw "Age can't be negative";
} // do something
}try {
testAge(-2);} catch(e) {
print(e.message);}
Ternary Conditional Operator
Swift
let contentHeight = 40let hasHeader = truelet rowHeight = contentHeight + (hasHeader ? 50 : 20)
Dart
final contentHeight = 40;final hasHeader = true;final rowHeight = contentHeight + (hasHeader ? 50 : 20);
Strings and Characters
อันนี้ใช้เหมือนกันเลยครับ
Swift
let someString = "Some string literal value"let quotation = """The White Rabbit put on his spectacles. “Where shall I begin,please your Majesty?” he asked.“Begin at the beginning,” the King said gravely, “and go ontill you come to the end; then stop.”"""
Dart
const someString = "Some string literal value";const quotation = """The White Rabbit put on his spectacles. “Where shall I begin,please your Majesty?” he asked.“Begin at the beginning,” the King said gravely, “and go ontill you come to the end; then stop.”""";
String Interpolation
Swift
let name = "Swift"
let version = 5.4
let people = People(name: "Apple, age: 4)print("my name is \(name) version: \(version) age: \(people.age)")
Dart
final name = "Flutter";
final version = 2.0;
final people = People("Apple", 4);print("my name is $name version: ${version} age: ${people.age}");
// For simple variable you can omit {}
Arrays
Swift
// Create Empty Array
var someInts = [Int]()
someInts.append(5)var shoppingList: [String] = [“Eggs”, “Milk”]
print(shoppingList[0]) // Print "Eggs"
print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."shoppingList.append("Fish")
for item in shoppingList {
print(item)
}// Prints
// Eggs
// Milk
// Fish
var peopleList: [People] = [People(name: "Amorn"), People(name: "Swift"), People(name: "Apple")]for people in peopleList {
print(people.name)
}// Prints
// Amorn
// Swift
// Apple
Dart
// Create Empty Array
var someInts = <int>[];
someInts.add(5);var shoppingList = ["Eggs", "Milk"];
print(shoppingList[0]); // Print "Eggs"
print("The shopping list contains ${shoppingList.length} items.") ;
// Prints "The shopping list contains 2 items."shoppingList.add("Fish")for (String item in shoppingList) {
print(item);
}// Prints
// Eggs
// Milk
// FishList<People> peopleList = [People("Amorn"), People("Swift"), People("Apple")];for (People people in peopleList) {
print(people.name);
}// Prints
// Amorn
// Swift
// Apple
Dictionaries or map
Swift
var fruitDictionary: [String: Int] = [“Apple”: 30, “Banana”: 50, "Carrot": 70, "Durian": 100]// Access variable in key
let durianPrice = fruitPrice["Durian"]for (fruitName, fruitPrice) in fruitPrice {
print("\(fruitName): \(fruitPrice)")
}// Prints
// Apple: 30
// Banana: 50
// Carrot: 70
// Durian: 100
Dart
Map<String, int> fruitDictionary = {"Apple": 30, "Banana": 50, "Carrot": 70, "Durian": 100};// Access variable in key
final durianPrice = fruitPrice["Durian"];for (MapEntry e in fruitDictionary.entries) {
print("${e.key}: ${e.value}")
}// Prints
// Apple: 30
// Banana: 50
// Carrot: 70
// Durian: 100
For-In Loops
Swift
for index in 0…5 {
print('hello \(index)')}
Dart
for (int i = 0; i < 5; i++) {
print('hello ${i}');
}
Conditional Statements
Swift
var temperatureInFahrenheit = 30if temperatureInFahrenheit <= 32 {print(“It’s very cold. Consider wearing a scarf.”)} else if temperatureInFahrenheit > 32 && temperatureInFahrenheit < 72 {
print(“It’s cozy, I like it.”)
} else {
print("too hot!!!")}
Dart
var temperatureInFahrenheit = 30;if (temperatureInFahrenheit <= 32) {print(“It’s very cold. Consider wearing a scarf.”);} else if (temperatureInFahrenheit > 32 && temperatureInFahrenheit < 72) {
print(“It’s cozy, I like it.”);
} else {
print("too hot!!!");}
Switch
Swift
let someCharacter: Character = “z”switch someCharacter { case “a”: print(“The first letter of the alphabet”) case “z”: print(“The last letter of the alphabet”) default: print(“Some other character”)
}enum Language {
case english
case thai
case spanish}let language: Language = .english
switch language {
case .english:
print("I speak English")case .thai:
print("I speak Thai")case .spanish:
print("I speak Spanish")}
Dart
final someCharacter = "z";switch (someCharacter) {case "a":
print("The first letter of the alphabet");
break;case "z":
print("The last letter of the alphabet");
break;default:
print("Some other character");
break;
}enum Language {
english,
thai,
spanish
}final Language language = Language.english;switch language {
case Language.english:
print("I speak English");
break; case Language.thai:
print("I speak Thai");
break; case Language.spanish:
print("I speak Spanish");
break;
}
Functions
Swift
// Function Parameters and Return Values
func greet(person: String) -> String {
let greeting = “Hello, “ + person + “!”
return greeting
}// Functions With Multiple Parameters
func greet(person: String, alreadyGreeted: Bool) -> String { if alreadyGreeted { return greetAgain(person: person) } else { return greet(person: person) }}// Functions Without Return Valuesfunc greet(person: String) { print(“Hello, \(person)!”)}greet(person: “Dave”)// Prints “Hello, Dave!”// Default Parameter Values
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// If you omit the second argument when calling this function, then// the value of parameterWithDefault is 12 inside the function body.}someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6)
// parameterWithDefault is 6someFunction(parameterWithoutDefault: 4)
// parameterWithDefault is 12
Dart
// Function Parameters and Return Values
// Dart has single line return functionString greet(person: String) => "Hello, $person !";orString greet(String person) {
final greeting = "Hello, $person !";
return greeting;
}// Functions With Multiple Parameters
String greet(String person, bool alreadyGreeted) { if (alreadyGreeted) { return greetAgain(person); } else { return greet(person); }}// Functions Without Return Valuesvoid greet(String person) { print("Hello, $person!");}greet("Dave"); // Dart doesn't have Argument Labels// Prints “Hello, Dave!”// Default Parameter Values
void someFunction(int parameterWithoutDefault, {int parameterWithDefault = 12}) {}someFunction(3, parameterWithDefault: 6);
// parameterWithDefault is 6someFunction(4);
// parameterWithDefault is 12
Closures
Swift
let numbers = [0, 1, 2, 3, 4]
let reversedNumbers = numbers.sorted(by: { $0 > $1 } )
// reversedNames is equal to [4, 3, 2, 1 , 0]
Dart
final numbers = [0, 1, 2, 3, 4];
numbers.sort((a, b) => b.compareTo(a));// numbers is equal to [4, 3, 2, 1 , 0]
Enumerations
Swift
enum CompassPoint: String { case north case south case east case west}// Associated Values
enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String)}// Iterating over Enumeration Cases
enum Beverage: CaseIterable {
case coffee, tea, juice
}let numberOfChoices = Beverage.allCases.count
print(“\(numberOfChoices) beverages available”)for beverage in Beverage.allCases {
print(beverage)
}// coffee
// tea
// juice
Dart ไม่มี Enum แบบซับซ้อนเหมือน Swift จะมีแค่ Enum พื้นฐานไว้ใช้สำหรับ Switch Case เท่านั้น
Structures and Classes
Swift
จะมีคอนเซ็ปต์ Value Types คือ Struct และ Reference Type คือ Class
struct Resolution {var width = 0var height = 0}class VideoMode {var resolution = Resolution()let interlaced: Boollet frameRate: Doublelet name: Stringinit(name: String, frameRate: Double, interlaced: Bool) {
self.name = name
self.frameRate = frameRate
self.interlaced = interlaced
}}
Dart
จะไม่มีคอนเซ็ปต์ส่วนนี้ ทุกอย่างคือ Reference Types ทั้งหมด
class Resolution {var width = 0;var height = 0;}class VideoMode {
Resolution resolution = Resolution(); final bool interlaced; final Double frameRate; final String name;// Automatic initializers in constructors
// if you don't have require, it will have value as null, if you forget to add valueVideoMode({
@required this.interlaced, @required this.frameRate, @required this.name,});}
Computed Properties
Swift
struct Rect { var origin = Point() var size = Size() var center: Point { get { let centerX = origin.x + (size.width / 2) let centerY = origin.y + (size.height / 2) return Point(x: centerX, y: centerY) } set(newCenter) { origin.x = newCenter.x — (size.width / 2) origin.y = newCenter.y — (size.height / 2) }// Read only Properties
var isSizeZero: Bool {
return size.width == 0.0 && size.height == 0.0
}}}
Dart
class Rect { var origin = Point(); var size = Size();
int get center { final centerX = origin.x + (size.width / 2); final centerY = origin.y + (size.height / 2); return Point(centerX, centerY);
} void set center(Point newCenter) {
origin.x = newCenter.x — (size.width / 2); origin.y = newCenter.y — (size.height / 2); }
// Single line return will use => bool get isSizeZero => (size.width == 0.0 && size.height == 0.0);}
และมาถึงอันสุดท้าย ที่ทำผมงงตาแตกตอนเจอครั้งแรก
Cascades
คือการที่เราไม่ต้องเขียนตัวแปรซ้ำๆ เราสามารถย่อให้สั้นลงได้ ซึ่ง Swift จะไม่มี Cascades
Swift
let people = People()
people.tag = 1
people.name = "Apple"
people.lastname = "Swift"
people.age = 20
people.isGraduated = fasle
Dart
People()
..tag = 1
..name = "Apple"
..lastname = "Swift"
..age = 20
..isGraduated = false
ผมคิดว่าน่าจะครอบคลุมเบสิกที่ใช้ทั่วไปได้ระดับหนึ่งนะครับ สำหรับคนที่จะลอง Dart แบบที่ยากกว่านี้จะไม่ได้มีเขียนไว้ครับ เพราะเขียนได้อีกยาวออกมาเป็นหนังสือเลย 😅 ส่วนต่อไปจะเกี่ยวกับเรื่อง UI
User Interface
ในบทความนี้จะเทียบระหว่าง UIKit กับ Dart ซึ่งส่วนของ SwiftUI นั้นผมไม่ได้เขียนครอบคลุมเพราะว่ายังไม่ค่อยคล่องเรื่อง SwiftUI นัก ไว้มีประสบการณ์มากกว่านี้จะนำมาเล่านะครับ
UIKit เป็น Imperative UI ในขณะที่ Dart เป็น Declarative UI ต่างกันยังไง ลองไปอ่านดูได้ครับ ถ้าให้เล่าง่ายๆ Declarative UI จะคล้ายกับ HTML Style ครับผม
ใน Swift UIKit หลักๆ เราจะรู้จักตามนี้
- Storyboard
- Nib Files
- ViewController
- UIView
UI ใน Dart ทั้งหมดจะทำด้วยโค้ดเท่านั้น ไม่มี Storyboard หรือไฟล์ UI เหมือนกับของ Swift แต่ยังดีที่ Flutter มี Hot Reload ทำให้ง่ายต่อการทดลองวาง Layout แม้ว่าจะมองไม่เห็น UI ก็ตาม
ทุกอย่างใน Dart จะใช้ Widget ไม่ว่าจะปุ่ม รูป หรือแม้กระทั่งการเว้นช่อง Spacing, Padding, Margin หรือ Layout เช่น Row, Column ก็เป็น Widget ทั้งสิ้น แปลกดีใช่มั้ยครับ ViewController กับ UIView ก็จะนับเป็น Widget เช่นกัน
ใน UIKit คนเขียนสามารถอัพเดต UI ด้วยการใส่ค่าใหม่ๆ ลงไป UI ของ Swift ก็จะถูกอัพเดต ในขณะที่คอนเซ็ปต์ Dart นั้น UI จะไม่สามารถถูกใส่ค่าใหม่ได้ ทุกอย่างเป็น Immutable ซึ่งการจะอัพเดต UI ได้ต้อง Rebuild ทั้ง UI ใหม่หมดเลย โดยจะมีคอนเซ็ปต์ Stateful กับ Stateless Widgets
- StatelessWidget Widget ที่ไม่สามารถทำการเปลี่ยนค่าได้ โดย UI พื้นฐานทั่วไปของ Dart จะเป็น Stateless ทั้งหมด เช่น ข้อความ ปุ่ม รูป เหมาะสำหรับการทำ Hardcore UI
- StatefulWidget Widget ที่สามารถอัพเดต UI ได้ตามเงื่อนไข ตัวแปร หรือค่าต่างๆ ที่ได้มาจาก API
พอฟังแล้ว ทำไมเราไม่ให้ทุกอย่างเป็น StatefulWidget หมดเลยล่ะ มีด้วยเหรอ UI ที่ห้ามอัพเดต แล้วจะเขียนแอปกันยังไง คอนเซ็ปต์นี้หลักๆ ทำไว้เพื่อ Performance ครับ สามารถอ่านเพิ่มได้ที่ลิงก์ด้านล่างนี้
ส่วนที่ว่าห้ามอัพเดต ทำให้ Dart มีคอนเซ็ปต์ Build ครับ คือทำลายของเก่าทิ้งแล้วสร้างใหม่หมดเลย ดู UI ตัวใหม่ ไม่ได้อัพเดตของเก่าแต่อย่างใด
NavigationController
Dart
เท่าที่ผมเจอมา Root ของแอปยังไงก็ใช้ MaterialApp กับ Scaffold คู่กัน เหมือนกับคนที่ทำแอป iOS ยังไงก็ใช้ NavigationController ไว้ตั้งแต่ต้นเลย
MaterialAppScaffold
- MaterialApp เป็นการบอกว่าแอปเราจะใช้ Material Design เป็น Google Design
- Scaffold เหมือนกับ NavigationController ซึ่ง Widget ตัวนี้จะมีพวก AppBar, BottomBar และ Components อื่นๆ ที่จำเป็นมาให้
ถ้าอยากจะใช้ iOS Design จริงๆ สามารถเลือกใช้ CupertinoApp แทน แต่ผมว่าอย่าเลยครับ เพราะ MaterialApp ดีกว่า บางตัวใน CupertinoApp ทำไม่ได้ด้วยซ้ำ ซึ่งผมเข้าใจว่าเขาน่าจะโฟกัสตรงปรับ MaterialApp มากกว่า ก็ Product ของ Google เขาเองน่ะ
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Home'),
),
)
)
เมื่อทำตามวิธีข้างต้น จะได้ผลตามด้านล่างครับ
สำหรับคนที่อยากอ่านรายละเอียดเพิ่มเติมครับ
UITabBarController
Dart
UITableView & UICollectionView
Dart
มีแค่ Widget คือ ListView
ถ้าเราจะใช้ข้อมูลเยอะๆ จำเป็นต้องใช้ ListView.builder
แทนครับ แต่ใน Swift สามารถใช้ TableView กับข้อมูลเยอะๆ ได้เลย
TableViewCell Or CollectionViewCell
Dart
ใช้ ListTiles หรือจะใช้ Custom Widget ทำ UI แทนได้ครับ
UILabel
Dart
UIButton
Dart
มีปุ่มหลากหลายดีไซน์เลยครับ ลองเลือกจากด้านล่างได้เลย
- Button แบบมีเงา
- Button แบบ Flat Design
- Floating Button แบบ Android
- Button แบบมีไอคอน
- Button แบบมีคลื่นตอนคลิก
- Button แบบมีข้อความ
- Button แบบมีแต่เส้น Outline
หรือถ้าอยากทำปุ่มเอง สามารถนำ TapGesture ไปคลุมไว้ที่ Widget ไหนก็ได้ครับ เท่านั้นก็จะทำงานเหมือนปุ่มแล้ว
UIImageView
Dart
จะมีหลายแบบใน Widget เดียวครับ ไม่ว่าจะเป็นรูปไอคอนหรือรูปโหลดจากเน็ตเวิร์ค ใส่อันเดียวกันได้หมดเลย
Threading & Asynchronicity
Dart
Dart เป็นภาษา Single Thread แปลว่าไม่สามารถสั่งให้ทำงานเบื้องหลังได้ครับ จะเหมือนกับ node.js
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
print("This message will show after API is finished"); setState(() {
widgets = jsonDecode(response.body);
});
}
จากโค้ดด้านบน Await จะเป็นคำสั่งที่บอกว่า “ทำบรรทัดนี้ให้เสร็จก่อนที่จะไปบรรทัดต่อไป รอนานแค่ไหน ผมก็จะรอ” และหลังจากนั้นผมจะนำค่าที่ได้ไปใส่ในตัวแปร แล้ว Rebuild ทั้งหน้าใหม่ด้วยคำสั่ง setState()
แต่ถ้าอยากได้ Callback คล้ายๆ iOS เราสามารถเปลี่ยน Await ให้เป็น Then ได้แบบด้านล่าง
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.get(dataURL).then((response) {
setState(() {
widgets = jsonDecode(response.body);
});
});
print("This message will show without waiting for network");}
ที่ต่างกันคือ Dart จะทำงานบรรทัดต่อไปเลยไม่รอ และถ้าทำงานเสร็จเมื่อไหร่ จะกระโดดไปทำงานใน Block ทันที ในขณะที่แบบแรกคือจะรอจนกว่าจะเสร็จ จึงจะทำงานบรรทัดต่อไปครับ สามารถอ่านรายละเอียดเพิ่มเติมได้ที่นี่
ViewLifeCycle
ผมเจอบทความที่อธิบายได้ละเอียดที่นี่
UIView
awakeFromNibUIViewController
viewDidLoad
viewWillAppear
viewDidAppear
viewWillDisappear
viewDiddisappear
Dart
Widget
initState() - จะถูกเรียกครั้งแรกครั้งเดียว เวลาเข้ามาที่หน้า UI จะคล้้ายๆกับ viewDidLoad ไม่คล้าย init นะครับ ผมว่าเราไม่มี nib filebuild() - จะเป็น function ที่บอกว่า UI หน้านี้จะ build อะไรบ้าง จะคล้ายๆกับ draw(_:) เพราะว่าคำสั่งนี้ build จะถูกเรียกตลอดเวลา เวลามีการเปลี่ยน UIsetState() - สั่งให้ Widget เรียก build() ใหม่ จะคล้ายๆกับ setNeedLayout ใน Swiftdispose() - เหมือน deinit ครับ ไว้ clear memory ที่ไม่จำเป็นจะใช้แล้วเช่นพวก subscriber
อันนี้จะเป็น 4 ฟังก์ชันหลักใน Widget UI ที่เราใช้กันบ่อยๆ ยังมีฟังก์ชันอื่นๆ อีกนะครับ แต่ส่วนใหญ่จะไม่ได้ใช้เท่าไหร่
มาถึงสิ่งสุดท้ายที่สำคัญมาก ซึ่ง Swift ไม่มี แต่ Flutter ใช้เยอะมาก
BuildContext
จริงๆ แล้วผมเองยังแอบงงๆ กับตัวนี้เหมือนกันครับ แต่จะพยายามอธิบายให้ดีที่สุด 😢 มันคือ Object ที่เป็น Reference Widget นั้นๆ ซึ่งสามารถส่งค่าผ่านไป Object อื่นๆ ได้ โดยทำงานเหมือนเป็น Reference ให้
Architecture
Swift
iOS Developers ส่วนใหญ่จะใช้ MVC, MVVM, Clean Swift ไม่ก็ VIPER กัน
Dart
จะมีคอนเซ็ปต์คล้ายๆ กัน แต่ส่วนใหญ่จะใช้ BLoC (Business Logic Component) คล้ายๆ MVVM แหละครับ คอนเซ็ปต์เดียวกัน ชื่อต่างกัน ก็คือหลักการที่เราเอา Business Logic ออกจาก View เพื่อที่จะได้ทำการเทสได้ แต่ Flutter Developer จะชอบใช้คู่กับ StreamBuilder
เป็น Widget ไว้สำหรับ Binding View เข้ากับ Data โดยจะเป็น Observable Pattern เหมือนกับใน Swift ที่ถ้ามีการอัพเดต Data จะทำการอัพเดต UI ให้โดยอัตโนมัติ ซึ่งใน Swift จะเป็น Optional ครับ คนส่วนใหญ่ที่ผมเจอจะไม่ได้ใช้กัน ในขณะที่ Flutter Developer จะใช้ Pattern นี้กันเกือบทุกคนและเกือบทุกที่ครับ ประหนึ่ง SwiftUI Developers ที่จะใช้ Combine Framework กัน
Dependency Manager
Swift
Cocoapods, Carthage, Swift Package Manager
Dart
Pub
ถ้าจะหา Lib ใดๆ สามารถดูได้ที่ https://cocoapods.org/
Must-Have Libs
Swift
Alamofire
KingFisher
SnapKit
Lottie
Hero
อันนี้แค่ตัวอย่างนะครับ แต่ละคนน่าจะใช้ไม่เหมือนกัน
Dart
ใช้ Libs หมด เพราะทุกอย่างใน Flutter ต้องการ Libs ทั้งหมด ด้วยความที่ Flutter เป็นแค่ UI Framework ไม่ได้มี Functionality อื่นๆ ดังนั้นไม่ว่าจะเป็นกล้อง GPS รูปถ่าย หรือแม้กระทั่งเปิดเว็บไซต์ยังต้องใช้ Libs เลย 😄 ถึงจะไม่ได้รวมเข้าไปใน Flutter Framework ให้ แต่ทาง Flutter Team ได้ทำ Libs พื้นฐานทุกตัวให้หมดแล้ว ไม่ต้องกังวลไปอย่างใดครับ
สำหรับบทความนี้น่าจะถูกอัพเดตเรื่อยๆ นะครับ เผื่อผมยังหลุดหรือพลาดอะไรไป จะได้นำมาเสริมให้ครับผม สำหรับชาว iOS ทั้งหลาย หวังว่าบทความนี้จะช่วย iOS ที่อยากลองเขียน Dart ได้นะครับ และสุดท้ายนี้… ถึงคุณจะเขียน Dart ยังไง ทักษะ iOS ก็ยังจำเป็นต้องมีในการทำ Flutter ครับ
สำหรับชาวเทคคนไหนที่สนใจเรื่องราวดีๆแบบนี้ หรืออยากเรียนรู้เกี่ยวกับ Product ใหม่ๆ ของ KBTG สามารถติดตามรายละเอียดกันได้ที่เว็บไซต์ www.kbtg.tech