แผนการเรียนรู้
/
Flutter Mobile Dev | Week 4 • Parts 1 & 2

สัปดาห์ที่ 4: ฟอร์มและการรับค่าข้อมูล (Forms & Input Validation)

🎯 สารบัญโครงสร้างเนื้อหาหมวดปฏิบัติการที่ 1 และ 2

⌨️ หมวดที่ 1: การรับค่าและควบคุมวิดเจ็ตข้อความ
🛡️ หมวดที่ 2: ระบบฟอร์มและการตรวจสอบความถูกต้อง
💡 คำชี้แนะ: คลิกหัวข้อบนสารบัญเพื่อข้ามไปยังหน้านั้นโดยตรง หรือใช้ปุ่มควบคุมด้านล่างเพื่อไล่ดูตามลำดับการสอน
Slide 1 • Input Widgets Comparison

การเปรียบเทียบวิดเจ็ตรับข้อมูล: TextField vs. TextFormField ⚔️

TextField (ระดับพื้นฐาน)

เป็นวิดเจ็ตอิสระสำหรับรับข้อมูลตัวอักษรเดี่ยวจากแป้นพิมพ์ เหมาะสำหรับการรับข้อมูลทั่วไปโดยไม่ต้องคำนึงถึงความสัมพันธ์กับตัวควบคุมภายนอก [1].

  • ดึงค่าทำงานผ่านการผูก Listener หรือตรวจเช็ค onChanged [2].
  • ข้อจำกัด: ไม่มีระบบตรวจสอบสิทธิ์และพ่นข้อความ Error สีแดงให้ในตัว (ต้องเขียนเงื่อนไขควบคุม UI เอง).

TextFormField (ระดับมืออาชีพ)

เป็นวิดเจ็ตพิเศษที่สืบทอดความสามารถมาจาก FormField ถูกออกแบบให้ทำงานร่วมกับวิดเจ็ต Form ด้านบน [1].

  • รองรับคุณสมบัติ validator สำหรับรันตรวจสอบค่าและคายค่าคำแจ้งเตือนได้ทันที [3].
  • มีคุณสมบัติ onSaved ใช้สั่งบันทึกสถานะลงโมเดลเมื่อรันฟอร์มผ่านเกณฑ์สำเร็จ.
🔗 เอกสารสอนติดตั้งและสไตล์กล่องป้อนข้อความระดับมาตรฐาน (Flutter Cookbook): https://docs.flutter.dev/cookbook/forms/text-input 🚀
Slide 2 • Controlling Inputs with controllers

การจัดการและล้างข้อมูลข้อความด้วย TextEditingController 🎮

`TextEditingController` คือกลไกเชื่อมโยงหน้าจอ View และโค้ดหลังบ้าน เพื่อเรียกอ่านค่าข้อความ (Read) เปลี่ยนแปลงเนื้อหา (Update) และล้างกล่องข้อความทิ้งเมื่อกดยืนยันสำเร็จ [2]:

// 1. การประกาศสร้างอินสแตนซ์และผูกควบคุมวิดเจ็ต

class _MyFormState extends State<MyForm> {

final _usernameController = TextEditingController();

// ⚠️ กฎเหล็ก: ต้องทำลายล้าง Controller ใน dispose เสมอเพื่อเลี่ยงหน่วยความจำรั่วไหล (Memory Leaks)

@override

void dispose() {

_usernameController.dispose();

super.dispose();

}

@override

Widget build(BuildContext context) {

return TextFormField(

controller: _usernameController,

decoration: const InputDecoration(labelText: 'ชื่อผู้ใช้'),

);

}

}

Slide 3 • Keyboard & Focus Management

การควบคุมแป้นพิมพ์และการโยกย้าย Focus ด้วย FocusNode ⌨️

ในการพิมพ์ข้อมูลบนแบบฟอร์มที่มีหลายๆ ช่องกรอก ยอดแย่ที่สุดคือการบีบให้ผู้เรียนต้องเอานิ้วกดจิ้มเปลี่ยนช่องกรอกด้วยตนเอง เราจึงใช้ `FocusNode` เข้ามาควบคุมระบบโฟกัสและทิศทางแป้นพิมพ์:

เมื่อพิมพ์ชื่อผู้ใช้เสร็จเรียบร้อยและกดยืนยันปุ่มส่งบนแป้นพิมพ์ (เช่น textInputAction: TextInputAction.next) เราสามารถเขียนรหัสย้ายโฟกัสข้ามพาสไปหาช่องรหัสผ่านได้ทันทีดังนี้:

FocusScope.of(context).requestFocus(_passwordFocusNode);

🎯 แผนภาพจำลองการส่งต่อโฟกัสแบบอัตโนมัติ

Username Field I Next Action ⌨️ Password Field
🔗 คู่มือและชุดคำสั่งควบคุม Focus ในช่องรับข้อความ: https://docs.flutter.dev/cookbook/forms/focus 🚀
Slide 4 • Form Widget & GlobalKeys

โครงสร้างหลักของ Form และการใช้ตัวนำทาง GlobalKey 🏗️

ในการสร้างแบบฟอร์มตรวจสอบคุณค่าข้อมูล เราจะใช้ตัวห่อหุ้มชั้นนอกสุดชื่อวิดเจ็ต Form เพื่อประสานความสัมพันธ์และควบคุมตรวจสอบความปลอดภัยของทุกๆ กล่อง TextFormField ภายใต้ร่มเงาเดียวกัน [1].

🛡️ GlobalKey<FormState> คืออะไร?

คือคีย์ลับสากลเฉพาะตัวที่สร้างขึ้นมาเพื่อผูกติดกับ Form และมอบอำนาจให้เราสามารถสั่งรันประมวลผลคำสั่งตรวจสอบความถูกต้องข้อมูลผ่าน State จากภายนอกวิดเจ็ตได้โดยตรง [3].

🎯 โครงสร้างความสัมพันธ์ของโหนด Form ต้นไม้

Form (key: _formKey) TextFormField (Email) TextFormField (Pass)
🔗 อ่านคู่มือทางการโครงสร้าง Form และการตั้งค่าคีย์ตรวจสอบสิทธิ์: https://docs.flutter.dev/cookbook/forms/validation 🚀
Slide 5 • Form Validation Logic

การออกแบบเงื่อนไขคัดกรองข้อมูลด้วยคุณสมบัติ validator 🧪

ฟังก์ชัน `validator` จะทำงานดักตรวจสอบสายตัวอักษรดิบที่ผู้ใช้งานพิมพ์เข้าสู่กล่องกรอก หากข้อมูลถูกต้องครบถ้วนตามกฎระบบ ฟังก์ชันจะต้องคืนค่ากลับเป็น null แต่หากตรวจพบค่าผิดกฎ ให้พ่นส่งกลับเป็นข้อความ String ผลลัพธ์เตือนภัย ทันที [3]:

// การเขียนเงื่อนไขคัดกรองความมั่นคงของกล่องอีเมล (Email Validator)

TextFormField(

decoration: const InputDecoration(labelText: 'อีเมลแอดเดรส'),

validator: (value) {

// 1. ตรวจช่องว่างข้อมูล

if (value == null || value.isEmpty) {

return 'กรุณากรอกอีเมลสะกดจริง';

}

// 2. ตรวจสอบเงื่อนไขรูปแบบอีเมลด้วยนิพจน์ Regular Expression (Regex)

if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {

return 'รูปแบบโครงสร้างอีเมลไม่ถูกต้อง';

}

return null; // 🟢 ข้อมูลผ่านเกณฑ์อย่างไร้กังวล

},

);

🔗 ดูโครงร่างคู่มือการทำแบบฟอร์มตรวจสอบสิทธิ์ทั้งหมด (Flutter Cookbook): https://docs.flutter.dev/cookbook/forms/validation 🚀
Slide 6 • Triggering Form Validation

การทริกเกอร์ตรวจสอบความเสถียรข้อมูลผ่าน validate() ⚡

เมื่อผู้ใช้งานกดปุ่มล็อกอินหรือยืนยันคำขอสมัครสมาชิก เราจะเขียนโค้ดเพื่อควบคุมการสั่งการคีย์อ้างอิงสากล _formKey.currentState!.validate() เพื่อสั่งให้ตรวจสอบความถูกต้องทันที [3]:

1. User Presses Button 🖱️ ยิงกระตุ้นลอจิก onPressed ของปุ่มยืนยัน
2. Run validate() ⚡ ระบบเรียกตรวจสอบฟังก์ชัน validator ทุกช่องกรอก
3. Evaluate Outcome 🚦 หากถูกต้อง (true) ดึงโค้ดยิง API หากผิดปล่อย Error แสดง UI

ElevatedButton(

onPressed: () {

// รันตรวจเช็คฟอร์มประมวลผล หากข้อมูลผ่านเกณฑ์ทั้งหมด

if (_formKey.currentState!.validate()) {

// 🟢 เรียกส่งลอจิก ViewModel ดึงค่าไปรันประมวลผลเซิร์ฟเวอร์ต่อ

ScaffoldMessenger.of(context).showSnackBar(

const SnackBar(content: Text('กำลังประมวลผลข้อมูลลงทะเบียน...')),

);

}

},

child: const Text('สมัครสมาชิก'),

);

Slide 7 • Registration Form Laboratory Work

ใบงานปฏิบัติการประจำคาบเรียน: พัฒนาระบบลงทะเบียนสมัครสมาชิก 📱🧪

ลงมือปฏิบัติด้วยตัวนักศึกษาเอง โดยนำความรู้คริปโทกราฟีของคู่คีย์ และการรับข้อมูลฟอร์ม มาร่วมกันเขียนสเกเลตันแบบฟอร์มลงทะเบียนสมาชิกที่ปลอดภัย มีเสถียรภาพ และสอดคล้องกับพอร์ตทางทฤษฎี:

📋 ความต้องการของระบบ (Form Specifications)

  • กล่องชื่อบัญชี (Username): ห้ามเป็นช่องว่างข้อมูลดิบเด็ดขาด [3]
  • กล่องอีเมล (Email): ตรวจรูปแบบสระและเครื่องหมาย @ ของสากลให้ถูกต้อง [3]
  • กล่องรหัสผ่าน (Password): ต้องระบุความยาวขั้นต่ำอย่างน้อย 8 ตัวอักษรเพื่อความปลอดภัย [3]

🧪 คำชี้ชวนการทดสอบปฏิบัติภารกิจ

ทดสอบรันผลลัพธ์ผ่านตัวเครื่องโทรศัพท์พกพาจำลอง และป้อนข้อมูลที่ผิดพลาดเพื่อเช็คสัญญาณพ่น Error UI สีแดงใต้กรอบทำงานอย่างลื่นไหล

🔗 ดูรายละเอียดโปรเจกต์และเปรียบเทียบชุดเทสจากโปรเจกต์ต้นแบบ Compass: https://github.com/flutter/samples/tree/main/compass_app 🚀
Slide 8 • Real-time Input Validation

การดักสแกนพฤติกรรมความถูกต้องแบบเรียลไทม์ (onChanged) 📡

คุณลักษณะ onChanged จะประมวลและรันลอจิกความถูกต้องทันทีที่ปลายนิ้วสัมผัสอักษรใดๆ บนแป้นพิมพ์ ซึ่งมีพฤติกรรมและแนวปฏิบัติเทียบกับระบบจัดเก็บคีย์บอร์ด Controller ดังนี้ [2]:

📡 การสแกนผ่าน onChanged (ระดับพื้นฐาน)

ยิงลอจิกตรวจสอบสถานะและวาดหน้าจอ Rebuild UI ซ้ำๆ แบบเบาตัว เหมาะกับการใช้ทำระบบกรองค้นหาทริปท่องเที่ยวแบบทันท่วงที (Live Filtering)

💾 การผูก Controller Listeners (ระดับโปรดักชัน)

นำเสนอการติดตั้ง Listener ดักสแกนพจนานุกรมในคลาส State เพื่อควบคุมและล็อกอินประเมินรูปแบบการกรอกได้ละเอียดและปลอดภัยสูง

💡 คำแนะนำจาก OWASP: การตรวจสอบฝั่งไคลเอนต์หน้าแอป Flutter (Client-Side Validation) ช่วยเพื่อความลื่นไหลของแอปพกพาเป็นสำคัญ แต่ห้ามลืมเขียนตรวจสอบสิทธิ์ร่วมกันฝั่งเซิร์ฟเวอร์หลังบ้าน Django (Server-Side Validation) เพื่อสกัดการลักลอบบันทึกรหัสแปลกปลอมเข้าฐานข้อมูลเด็ดขาด [3].
Flutter Mobile Dev | Week 4 • Parts 3 & 4

สัปดาห์ที่ 4: ฟอร์มและการรับค่าข้อมูล (Forms & Input Validation)

🎯 สารบัญโครงสร้างเนื้อหาหมวดปฏิบัติการที่ 3 และ 4

🎨 หมวดที่ 3: การจัดรูปแบบและยกระดับประสบการณ์ผู้ใช้งานฟอร์ม (UX)
🏗️ หมวดที่ 4: การเชื่อมโยงข้อมูลฟอร์มสู่สถาปัตยกรรมแอปพลิเคชัน (MVVM)
💡 คำชี้แนะ: คลิกหัวข้อบนสารบัญเพื่อข้ามไปยังหน้านั้นโดยตรง หรือใช้ปุ่มควบคุมด้านล่างเพื่อไล่ดูตามลำดับการสอน
Slide 10 • Input Aesthetics & InputDecoration

ยกระดับความสวยงามของฟอร์มด้วย InputDecoration 🎨

การจัดรูปแบบที่ดีช่วยสร้างประสบการณ์ผู้ใช้งาน (UX) ที่ราบรื่น โดยการเพิ่มคำแนะนำ (Hint) ป้ายกำกับ (Label) ไอคอนนำหน้า/ต่อท้าย (Prefix/Suffix) และเส้นขอบเมื่ออยู่ในสถานะปกติ โฟกัส หรือเกิดข้อผิดพลาด:

TextFormField(

decoration: InputDecoration(

// ป้ายกำกับแบบลอยตัวและไอคอนนำหน้า

labelText: 'อีเมลผู้ใช้',

hintText: 'example@email.com',

prefixIcon: const Icon(Icons.email_outlined),

// การตกแต่งเส้นขอบสไตล์กล่องมน (OutlineInputBorder)

border: const OutlineInputBorder(

borderRadius: BorderRadius.all(Radius.circular(12.0)),

),

focusedBorder: OutlineInputBorder(

borderSide: BorderSide(color: Colors.blue.shade400, width: 2.0),

borderRadius: const BorderRadius.all(Radius.circular(12.0)),

),

),

);

🔗 ดูรูปแบบและการตกแต่งกล่องข้อความระดับมาตรฐานเพิ่มเติม (Flutter Documentation): https://docs.flutter.dev/cookbook/forms/text-input 🚀
Slide 11 • Form Feedback with SnackBar

การแสดงผลการทำงานและแจ้งเตือนผู้ใช้ด้วย SnackBar 💬

เมื่อแบบฟอร์มผ่านเกณฑ์การตรวจสอบ (Validation) เรียบร้อยแล้ว การรายงานสถานะหรือผลลัพธ์เป็นสิ่งจำเป็นอย่างยิ่ง โดยทีมงานวิศวกร Flutter แนะนำให้แจ้งข้อมูลแบบไม่ขัดจังหวะสายตาผู้ใช้ด้วยการเรียก **`ScaffoldMessenger`** เพื่อวาด SnackBar:

// แนวทางรันข้อความด่วนแบบ SnackBar ระดับสากล

void _showSuccessFeedback(BuildContext context) {

ScaffoldMessenger.of(context).showSnackBar(

SnackBar(

content: const Text('บันทึกข้อมูลเรียบร้อยแล้ว!'),

backgroundColor: Colors.green.shade600,

duration: const Duration(seconds: 3),

action: SnackBarAction(

label: 'ตกลง',

textColor: Colors.white,

onPressed: () {},

),

),

);

}

💡 ข้อสังเกตความเสถียร: การใช้ ScaffoldMessenger.of(context) เป็นกลไกใหม่ที่ทำงานแยกอิสระจากสิทธิ์ควบคุม State ของ Scaffold เดิม ส่งผลให้ระบบทำงานได้อย่างต่อเนื่องแม้ผู้ใช้กดปิดหน้าจอดังกล่าวไปแล้ว.
🔗 ดูรายละเอียดคู่มือสอนแสดงข้อความความสำเร็จผ่าน SnackBar (Flutter Cookbook): https://docs.flutter.dev/cookbook/design/snackbars 🚀
Slide 12 • Keyboard Dismissal & GestureDetector

การปิดแป้นพิมพ์คีย์บอร์ดเมื่อสัมผัสพื้นที่ว่างด้วย GestureDetector ⌨️❌

ปัญหา UX ยอดนิยมในระบบมือถือคือ แป้นคีย์บอร์ดไม่ยอมยุบตัวลงเมื่อพิมพ์ข้อมูลเสร็จ วิธีแก้ปัญหาระดับโปรคือการหุ้มหน้าจอหลัก (`Scaffold`) ด้วยวิดเจ็ต **`GestureDetector`** เพื่อคอยดักตรวจจับสิทธิ์สัมผัส แล้วเรียกคืนโฟกัสผ่านคำสั่ง **`unfocus()`**:

// ห่อหุ้ม Scaffold ชั้นนอกสุดเพื่อดักล้าง โฟกัส (Focus Clear)

@override

Widget build(BuildContext context) {

return GestureDetector(

onTap: () {

// ตรวจสอบและยกเลิกสถานะโฟกัสของคีย์บอร์ด

FocusScopeNode currentFocus = FocusScope.of(context);

if (!currentFocus.hasPrimaryFocus) {

currentFocus.unfocus();

}

},

child: Scaffold(

appBar: AppBar(title: const Text('สมัครสมาชิก')),

body: const MyRegistrationForm(),

),

);

}

🔗 อ่านรายละเอียดเกี่ยวกับการจัดสรรพฤติกรรมและการดักรับค่าสัมผัส (Flutter Interactivity): https://docs.flutter.dev/ui/interactivity/gestures 🚀
Slide 13 • Obscure Password Toggle

สลับซ่อน/แสดงรหัสผ่าน (Obscure Text Toggle) 👁️🔒

เพื่อความปลอดภัยขั้นพื้นฐานและสอดคล้องกับมาตรฐานความปลอดภัยสากล การส่งรหัสผ่านต้องปิดบังอักษรดิบด้วยคุณลักษณะ obscureText: true และ open ช่องทางให้ผู้ใช้สลับมองรหัสผ่านได้ผ่านการคลิกไอคอนรูปดวงตา:

// สร้างตัวแปร boolean เพื่อสับเปลี่ยน State

bool _obscurePassword = true;

// พอร์ตโครงสร้างวิดเจ็ตใน InputDecoration

TextFormField(

obscureText: _obscurePassword,

decoration: InputDecoration(

labelText: 'รหัสผ่าน',

suffixIcon: IconButton(

icon: Icon(_obscurePassword ? Icons.visibility_off : Icons.visibility),

onPressed: () {

setState(() {

_obscurePassword = !_obscurePassword;

});

},

),

),

);

Compass App Mockup View
📱 แผนภาพตัวอย่างหน้าจอเข้ายืนยันสิทธิ์ที่มีการสลับปุ่มเปิด/ปิดตาของรหัสผ่านเพื่อความมั่นใจ
Slide 14 • MVVM Form Integration

การส่งข้อมูลฟอร์มสู่เลเยอร์ ViewModel (MVVM Binding) 🏗️

ตามคู่มือสากลของสถาปัตยกรรมแอปพลิเคชัน Flutter แบบ MVVM ชั้นหน้าจอ View จะต้องไม่ส่งค่าไปคุยกับเครือข่ายตรงๆ [1] แต่จะรวบรวมค่าข้อมูลดิบผ่านคำสั่ง onSaved หรือดึงสดผ่าน TextEditingController แล้วส่งต่อสิทธิ์ให้ **ChangeNotifier ViewModel** เป็นผู้ประสานงานหลัก [2, 3]:

// 1. View เรียกคำสั่งเมื่อ Validation ผ่านฉลุย

if (_formKey.currentState!.validate()) {

_formKey.currentState!.onSaved();

// เรียก ViewModel ประมวลผลคำขอล็อกอินผ่านคลัง Provider

context.read<LoginViewModel>().submitLogin(

email: _emailValue,

password: _passwordValue,

);

}

🔗 เชื่อมโยงสัปดาห์ที่ 12: ในระบบของเรา การล็อกอินจริงเดินทางผ่าน OIDC Redirect Flow — AuthRepositoryRemote.login() จะสั่ง Authenticator.authorize() ของแพ็กเกจ openid_client พาผู้ใช้ไปยืนยันตัวตนที่ django-oidc-provider แล้วนำ Credential (ID Token / Access Token) กลับมาเก็บใน Secure Storage แทนการส่งรหัสผ่านตรงถึง API
🔗 เจาะลึกการจัดทำระบบการประมวลผลคำสั่งแบบปลอดภัยผ่านเลเยอร์ (UI Layer Case Study): https://docs.flutter.dev/app-architecture/case-study/ui-layer 🚀
MVVM Architecture Flow
📊 ความสัมพันธ์ในแอปพลิเคชัน Compass: ฟอร์มใน View รวบรวมส่งต่อหา ViewModel เพื่อเตรียมส่งลงสู่ชั้น Repository เลเยอร์ล่างตามลำดับแบบทางเดียว (UDF) [4, 5]
Slide 15 • Server-Side Validation Errors

การจัดการข้อผิดพลาดข้อมูลจากฝั่ง REST API หลังบ้าน 📡🚨

แม้ว่าข้อมูลจะผ่านการดักกรองจากโค้ดเครื่องหน้าบ้าน (Client-Side Validation) สำเร็จ แต่อาจเกิดข้อผิดพลาดในการประมวลผลฝั่งเซิร์ฟเวอร์หลังบ้าน เช่น **"อีเมลซ้ำซ้อน" (Email Already Exists)** หรือ **"รหัสผ่านไม่ผ่านเกณฑ์วิเคราะห์จริง"** ในรูปโครงสร้างข้อมูล JSON:

// ปรับปรุง ViewModel ให้อัปเดต UI เมื่อได้รับข้อผิดพลาดรหัส HTTP 400 จาก Django

class LoginViewModel extends ChangeNotifier {

String? _serverErrorMessage;

String? get serverErrorMessage => _serverErrorMessage;

Future<void> submitLogin(String email, String password) async {

final result = await _authRepo.login(email, password);

if (result is Error) {

_serverErrorMessage = result.error.message; // ดึงข้อความล้มเหลวจากเซิร์ฟเวอร์ [6]

notifyListeners(); // สั่งสแกนและพ่นสีแดงเตือนบนหน้าจอ View [7]

}

}

}

# 🧪 จำลองข้อผิดพลาดฝั่ง server ด้วย HTTPie (backend: uv run manage.py runserver)

$ http POST :8000/api/bookings/ destination_name="" price=-5

↳ HTTP/1.1 400 Bad Request → {"price": ["Ensure this value is greater than 0."]}

⚠️ กฎเหล็กความปลอดภัย: นักศึกษาต้องเขียนสคริปต์ให้ดักเช็คค่าตอบรับ result.serverErrorMessage หากไม่เป็นว่างเปล่า (Null) ให้ดึงไปแสดงผลแบบกล่องเตือน หรือพ่นลงป้ายกำกับข้อผิดพลาดของช่องนั้นๆ โดยด่วน.
Slide 16 • Diverse Form Input Fields

วิดเจ็ตกรอกแบบฟอร์มประเภทจำกัดตัวเลือก (Selection Inputs) 🗳️

นอกจากการพิมพ์ข้อความทั่วไป ระบบแอปพลิเคชัน Compass ยังรองรับกล่องรับข้อมูลประเภทกำหนดสิทธิ์การตรวจสอบและการติ๊กเลือกชนิดต่างๆ ภายใต้วิดเจ็ตตระกูล **`FormField`** สำหรับควบคุมความต่อเนื่องในกล่องเดียวกัน [8, 9]:

🔽 DropdownButtonFormField

จำกัดขอบเขตข้อมูลที่นักท่องเที่ยวสามารถจองได้ (เช่น เลือกจุดหมายปลายทาง ทริปเมืองโตเกียว ลอนดอน) สามารถแนบฟังก์ชันตรวจสอบคุณค่า validator ได้เหมือนช่องพิมพ์ทั่วไป.

☑️ CheckboxListTile

ให้ยืนยันเงื่อนไขความสอดคล้องหรือความประสงค์พิเศษเพิ่มเติม (เช่น ต้องการไกด์นำเดินทางผู้เชี่ยวชาญภาษาไทย) บันทึกและควบคุมเป็นพารามิเตอร์ประเภท boolean ในระบบ View.

🔛 SwitchListTile

สวิตช์สำหรับเปิด/ปิดฟังก์ชันการทำงานบางอย่างแบบทันที เช่น เปิดปิดสิทธิ์การรับรายงานด่วนทริปลดราคาพิเศษผ่านกลไกระบบ Notification ในอุปกรณ์เคลื่อนที่.

🔗 ดูรูปแบบคู่มือประยุกต์จัดทำ Form ยืนยันสิทธิ์ชนิดต่างๆ (Flutter Cookbook): https://docs.flutter.dev/cookbook/forms/validation 🚀
Slide 17 • Week 4 Laboratory Assignment

ใบงานปฏิบัติการ: พัฒนาฟอร์มเพิ่มข้อมูลทริปเดินทางใหม่ 🧭🧪

นักศึกษาแต่ละกลุ่มจะต้องลงมือพัฒนาหน้าจอส่งต่อข้อมูลสลับจองทริปเดินทางใหม่ **`Create Booking Form`** ภายใต้โครงร่างสถาปัตยกรรมของโครงการ Compass ให้ใช้งานและรันงานได้สมบูรณ์ [10]:

🔥 เกณฑ์ปฏิบัติและข้อตกลงการทดสอบ (Specifications):

  • สร้างแบบฟอร์มรองรับคีย์บอร์ด Unfocus และแสดง SnackBar คล่องตัวเมื่อสำเร็จ
  • ช่องจุดหมาย (Destination): ใช้ DropdownFormField และห้ามปล่อยว่าง
  • ช่องงบประมาณ (Price): ตรวจสอบว่าต้องพิมพ์ระบุเป็นตัวเลขจำนวนเต็มบวกเท่านั้น
  • จัดส่งข้อมูลเข้าสู่ ViewModel เพื่อเตรียมพร้อมส่งต่อไปยัง REST API หลังบ้านจริง

🎯 ทิศทางการเรียนรู้นอกเหนือชั่วโมง

นักศึกษาสามารถตรวจสอบชุดคำสั่งและเทมเพลตฟอร์มรับส่งข้อมูลของโครงการตัวอย่างทางการบนกิธฮับ [4]:

Slide 18 • Week 4 Summary & Preview

สรุปบทเรียนสัปดาห์ที่ 4 และปูทางสู่ปฏิบัติการสัปดาห์ที่ 5 🏁

วันนี้เราได้เรียนรู้การขัดเกลาอินเทอร์เฟซรับข้อมูลอย่างประณีตผ่าน InputDecoration, แสดง SnackBar [8], ปิดคีย์บอร์ดอัตโนมัติ, สร้างปุ่มสลับซ่อนรหัสผ่าน และทำการผูกส่งข้อมูลอย่างมั่นคงปลอดภัยเข้าสู่เลเยอร์ ViewModel (MVVM) ได้เสร็จสิ้นเรียบร้อยแล้ว [2]

🚀 ปูทางสู่ สัปดาห์ที่ 5: Dependency Injection & Controlled Testing

  • Dependency Injection (DI): ถอดเปลี่ยนคลังเก็บข้อมูลข้ามสภาพแวดล้อมด้วย package:provider ทั่วทั้งแอป [11, 12]
  • Controlled Testing: เขียนคำสั่งและกระบวนการจำลองข้อมูลเพื่อทำ Unit Test บนเลเยอร์ ViewModel โดยปราศจากเงื่อนไขเครือข่ายเน็ตเวิร์ก [13, 14]
  • Widget Testing: เขียนโปรแกรมวาดและทดสอบสกรีน UI เพื่อทดลองคลิกสุ่มตรวจสอบการสะท้อนสถานะ (State) โดยอัตโนมัติ [13, 14]
🔗 คู่มือประเมินความปลอดภัยและการบริหารค่า Dependency ในสถาปัตยกรรม Flutter ล่วงหน้า: https://docs.flutter.dev/app-architecture/case-study/dependency-injection 🚀