StreamBuilder คือวิดเจ็ต Flutter ที่สร้างอินเทอร์เฟซใหม่โดยอัตโนมัติเมื่อได้รับข้อมูลใหม่จากสตรีมแบบอะซิงโครนัส แตกต่างจาก FutureBuilder ที่ทำงานกับผลลัพธ์เดี่ยว StreamBuilder รองรับการอัปเดต UI อย่างต่อเนื่องตลอดวงจรชีวิตทั้งหมดของ Stream ตามเอกสารอย่างเป็นทางการของ Flutter (2026) StreamBuilder ถูกใช้ในแอปพลิเคชันแบบเรียลไทม์: แชท ฟีดข่าว การตรวจสอบเซนเซอร์ และทิกเกอร์ทางการเงิน มันเป็นเครื่องมือสำคัญของการเขียนโปรแกรมแบบรีแอกทีฟ ที่ซึ่ง UI สะท้อนสถานะของข้อมูลโดยไม่ต้องเรียก setState ด้วยตนเอง
ประเด็นสำคัญ
StreamBuilder คือวิดเจ็ตจากแพ็คเกจ Flutter SDK ที่สมัครสมาชิก Stream และสร้างองค์ประกอบย่อยใหม่ทุกครั้งที่มีเหตุการณ์สตรีมใหม่ StreamBuilder รับออบเจ็กต์ Stream และส่งคืนวิดเจ็ตตามสแนปชอตล่าสุดที่ได้รับจากสตรีม
ในสถาปัตยกรรม Flutter StreamBuilder อยู่ในกลุ่มวิดเจ็ต Builder ที่แยกการสร้าง UI ออกจากสถานะข้อมูล แตกต่างจาก StatefulWidget ที่การเปลี่ยนสถานะต้องเรียก setState อย่างชัดเจน StreamBuilder ตอบสนองต่อเหตุการณ์แบบอะซิงโครนัสโดยอัตโนมัติ ทำให้โค้ดง่ายขึ้นและลดความเสี่ยงของข้อผิดพลาดในการซิงโครไนซ์
แตกต่างจาก FutureBuilder ที่จัดการค่าแบบอะซิงโครนัสเดียว StreamBuilder ออกแบบมาสำหรับสตรีมข้อมูลต่อเนื่อง FutureBuilder สิ้นสุดหลังจากได้รับผลลัพธ์แรก ในขณะที่ StreamBuilder ยังคงฟังสตรีมและอัปเดต UI ทุกครั้งที่มีเหตุการณ์ใหม่
StreamBuilder ถูกใช้ในทุกสถานการณ์ที่ข้อมูลเข้ามาอย่างต่อเนื่อง: การเชื่อมต่อ WebSocket, คอลแบ็กเซนเซอร์, การแจ้งเตือน Firebase, คิวเหตุการณ์ Bluetooth และการกระจายสถานะแอปพลิเคชันผ่าน BLoC ตามการวิเคราะห์โปรเจ็กต์ Flutter บน GitHub (2025) StreamBuilder อยู่ในสามอันดับวิดเจ็ต Builder ที่ใช้มากที่สุดร่วมกับ FutureBuilder และ LayoutBuilder
สรุป: ใช้ StreamBuilder ทุกที่ที่ UI ต้องสะท้อนข้อมูลที่เปลี่ยนแปลงอย่างต่อเนื่อง หลีกเลี่ยงการจัดการสถานะด้วยตนเองผ่าน StatefulWidget
StreamBuilder สมัครสมาชิก Stream ในขณะสร้างและยกเลิกการสมัครเมื่อวิดเจ็ตถูกทำลาย ทุกครั้งที่ Stream ส่งเหตุการณ์ StreamBuilder จะได้รับ AsyncSnapshot ใหม่และเรียกฟังก์ชัน builder เพื่อสร้าง UI ใหม่
กระบวนการประกอบด้วยสามขั้นตอน ขั้นแรก: StreamBuilder สร้างการสมัครสมาชิกไปยัง Stream ที่ส่งผ่านด้วยวิธีการ stream.listen ขั้นที่สอง: ในแต่ละเหตุการณ์ StreamBuilder อัปเดต AsyncSnapshot ภายในและทำเครื่องหมายวิดเจ็ตว่าสกปรกสำหรับการสร้างใหม่ ขั้นที่สาม: เฟรมเวิร์กเรียกฟังก์ชัน builder ด้วยสแนปชอตใหม่ และ UI แสดงข้อมูลปัจจุบัน
สำคัญ: StreamBuilder ใช้ StreamSubscription ภายใน หาก Stream ถูกส่งโดยตรง StreamBuilder จะสมัครสมาชิกครั้งหนึ่งระหว่างการเริ่มต้น หาก Stream เปลี่ยนแปลง (เช่น ระหว่างการสร้างผู้ปกครองใหม่) StreamBuilder จะยกเลิกการสมัครจากสตรีมเก่าและสมัครสมาชิกสตรีมใหม่ พฤติกรรมนี้ถูกควบคุมโดยพารามิเตอร์ initialData และ buildWhich ช่วยให้ปรับจำนวนการสร้างใหม่ให้เหมาะสม
สรุป: การเข้าใจวงจรชีวิตของการสมัครสมาชิกเป็นพื้นฐานของการใช้ StreamBuilder อย่างถูกต้อง การจัดการสตรีมที่ไม่ถูกต้องนำไปสู่การรั่วไหลของหน่วยความจำหรือข้อมูลที่ล้าสมัยใน UI
คุณสมบัติ connectionState ของออบเจ็กต์ AsyncSnapshot กำหนดว่า StreamBuilder อยู่ในขั้นตอนใดของการประมวลผลสตรีม มีสี่สถานะ: none, waiting, active, done
None คือสถานะเริ่มต้นเมื่อ Stream ยังไม่ได้เริ่มส่งข้อมูล ในสถานะนี้ snapshot.connectionState เท่ากับ ConnectionState.none และ snapshot.data เป็น null โดยปกติแล้วในสถานะนี้จะแสดงตัวยึดตำแหน่งหรือตัวบ่งชี้การรอ หาก Stream ไม่ได้ให้ข้อมูลเริ่มต้น StreamBuilder จะเริ่มในสถานะนี้
Waiting คือสถานะรอข้อมูลจากสตรีมแบบอะซิงโครนัส Stream ทำงานอยู่ แต่ข้อมูลยังมาไม่ถึง สถานะนี้เกิดขึ้น เช่น เมื่อโหลดข้อมูลจากเครือข่ายหรือเปิดการเชื่อมต่อระยะยาว ในสถานะนี้เป็นปกติที่จะแสดง CircularProgressIndicator หรือโครงกระดูกโหลด
Active — สตรีมกำลังส่งข้อมูลและ UI แสดงข้อมูลปัจจุบัน ในสถานะนี้ snapshot.hasData เป็นจริง และ snapshot.data มีค่าล่าสุดจากสตรีม หากสตรีมเป็น Broadcast Stream สถานะที่ใช้งานสามารถอยู่ร่วมกับการรอข้อมูลใหม่ได้
Done — สตรีมเสร็จสมบูรณ์ จะไม่มีข้อมูลใหม่ Snapshot.data มีค่าสุดท้ายที่ส่งก่อนปิดสตรีม หากสตรีมเสร็จสมบูรณ์สำเร็จ snapshot.hasError เป็นเท็จ สถานะนี้ใช้เพื่อแสดงผลลัพธ์สุดท้าย: ข้อความเช่น “โหลดเสร็จสิ้น” หรือการเปลี่ยนไปยังหน้าจอถัดไป
สรุป: เมื่อสร้าง UI ผ่าน StreamBuilder ต้องจัดการทั้งสี่สถานะเพื่อให้อินเทอร์เฟซแสดงการโหลด ข้อมูล ข้อผิดพลาด และความเสร็จสมบูรณ์อย่างถูกต้อง
StreamController คือคลาสจากแพ็คเกจ dart:async ที่สร้างและจัดการ Stream StreamController อนุญาตให้เพิ่มข้อมูล จัดการข้อผิดพลาด และปิดสตรีม ควบคุมวงจรชีวิตของมัน
StreamController มีสองประเภท: single-subscription (ผู้สมัครสมาชิกหนึ่งคน) และ broadcast (ผู้สมัครสมาชิกหลายคน) ตัวควบคุม single-subscription รับผู้ฟังเพียงคนเดียวในแต่ละครั้ง — การสมัครครั้งที่สองจะทำให้เกิดข้อยกเว้น ตัวควบคุม broadcast อนุญาตให้ StreamBuilder หลายตัวฟังสตรีมเดียวกันพร้อมกัน ซึ่งมีประโยชน์สำหรับ BLoC และสถานะแอปพลิเคชันที่ใช้ร่วมกัน
เมื่อสร้าง StreamController ผ่าน StreamController<T>.broadcast() ข้อมูลที่เพิ่มก่อนการสมัครครั้งแรกจะไม่ถูกเล่นซ้ำให้กับผู้สมัครใหม่ เพื่อรับค่าล่าสุดเมื่อเชื่อมต่อ ให้ใช้ BehaviourSubject จากแพ็คเกจ rxdart ซึ่งแคชเหตุการณ์ล่าสุด
หลังจากทำงานกับตัวควบคุมเสร็จแล้ว ต้องเรียก controller.close() การไม่เรียก close ทำให้เกิดการรั่วไหลของทรัพยากร: สตรีมยังคงเปิดอยู่ ผู้สมัครสมาชิกยังคงอยู่ในหน่วยความจำ และ GC ไม่ได้ปล่อยออบเจ็กต์ที่เกี่ยวข้อง
สรุป: ใช้ StreamController ด้วยการจัดการวงจรชีวิตที่ชัดเจน สำหรับสตรีม single-subscription ให้ใช้ตัวควบคุมมาตรฐาน สำหรับสถานะที่ใช้ร่วมกัน ให้ใช้ตัวควบคุม broadcast หรือ BehaviourSubject
ตัวอย่างที่ 1 แสดงตัวจับเวลานับถอยหลังโดยใช้ StreamController และ StreamBuilder
import 'dart:async';
class TimerWidget extends StatefulWidget {
const TimerWidget({super.key});
final StreamController<int> controller = StreamController<int>();
void startTimer() {
int count = 0;
Timer.periodic(Duration(seconds: 1), (timer) {
controller.sink.add(count++);
if (count > 10) {
controller.close();
timer.cancel();
}
});
}
}
ในตัวอย่าง มีการสร้างตัวควบคุมเพื่อสร้างตัวเลขตั้งแต่ 0 ถึง 10 ด้วยช่วงเวลา 1 วินาที หลังจากถึง 10 จะเรียก close และสตรีมสิ้นสุดลง StreamBuilder ที่สมัครสมาชิกสตรีมของตัวควบคุมนี้จะแสดงค่าใหม่แต่ละค่า
ตัวอย่างที่ 2 — การใช้ StreamBuilder กับ Broadcast Stream เพื่อแสดงข้อมูลจากหลายแหล่ง
final StreamController<String> broadcastController =
StreamController<String>.broadcast();
StreamBuilder<String>(
stream: broadcastController.stream,
initialData: 'Waiting for data...',
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Text('ข้อผิดพลาด: ${snapshot.error}');
}
return Text('ข้อมูล: ${snapshot.data}');
},
)
ตัวอย่างที่สองแสดงการจัดการทุกสถานะ: initialData สำหรับการแสดงเริ่มต้น waiting สำหรับตัวบ่งชี้การโหลด hasError สำหรับข้อผิดพลาด และ data สำหรับผลลัพธ์ที่สำเร็จ รูปแบบนี้เป็นมาตรฐานสำหรับโค้ดการผลิตกับ StreamBuilder
สรุป: ใช้ initialData เพื่อหลีกเลี่ยงหน้าจอว่างในครั้งแรก และจัดการ hasError เสมอเพื่อแสดงข้อผิดพลาดอย่างถูกต้องแก่ผู้ใช้
ข้อผิดพลาด 1: การสร้าง Stream ใหม่ทุกครั้งที่ผู้ปกครองสร้างใหม่ หาก Stream ถูกส่งผ่านนิพจน์ที่สร้างออบเจ็กต์ใหม่ทุกครั้งที่สร้าง StreamBuilder จะยกเลิกการสมัครจากสตรีมเก่าและสมัครสมาชิกสตรีมใหม่ ทำให้เกิดลูปการสร้างใหม่ไม่รู้จบ วิธีแก้ไข: ใช้ตัวแปร remembered หรือ StatefulWidget ที่มี Stream คงที่
ข้อผิดพลาด 2: ขาดการจัดการข้อผิดพลาด Stream สามารถส่งข้อผิดพลาดผ่าน controller.sink.addError และหาก builder ไม่ตรวจสอบ snapshot.hasError ผู้ใช้จะเห็นหน้าจอว่างหรือการโหลดไม่รู้จบ วิธีแก้ไข: ตรวจสอบ hasError เสมอและแสดงข้อความที่ชัดเจน
ข้อผิดพลาด 3: การรั่วไหลของหน่วยความจำเนื่องจาก StreamController ที่ไม่ได้ปิด หากตัวควบคุมไม่ได้ปิดใน dispose สตรีมยังคงมีอยู่และ GC ไม่ได้ปล่อยหน่วยความจำ วิธีแก้ไข: เรียก controller.close() ใน dispose และฟังเหตุการณ์ done สำหรับการดำเนินการสุดท้าย
ข้อผิดพลาด 4: การใช้ StreamBuilder กับฟังก์ชัน builder ที่ช้า เนื่องจาก builder ถูกเรียกทุกครั้งที่มีเหตุการณ์สตรีม การคำนวณหนักภายในนั้นทำให้เฟรมหลุด วิธีแก้ไข: ย้ายการคำนวณไปยัง isolate แยกต่างหากหรือใช้ Stream.map สำหรับการแปลงข้อมูล
สรุป: StreamBuilder เป็นเครื่องมือที่ทรงพลังแต่ต้องการความระมัดระวัง ติดตามวงจรชีวิตของ Stream จัดการข้อผิดพลาด และหลีกเลี่ยงการดำเนินการหนักใน builder
คำถามที่พบบ่อย
FutureBuilder ออกแบบมาสำหรับผลลัพธ์แบบอะซิงโครนัสเดียว: มันสมัครสมาชิก Future รับค่าหนึ่งค่าและสิ้นสุด StreamBuilder สมัครสมาชิก Stream ซึ่งสามารถส่งหลายค่าเมื่อเวลาผ่านไป และสร้าง UI ใหม่ทุกครั้งที่มีเหตุการณ์ใหม่
AsyncSnapshot คือออบเจ็กต์ที่ไม่สามารถเปลี่ยนแปลงได้ซึ่งประกอบด้วยสถานะการสมัครสมาชิกปัจจุบัน (connectionState) ค่าสุดท้ายที่ได้รับ (data) และออบเจ็กต์ข้อผิดพลาด (error) หากสตรีมส่งข้อยกเว้น
ข้อผิดพลาด ถูกจัดการผ่านคุณสมบัติ snapshot.hasError และ snapshot.error ในฟังก์ชัน builder หากสตรีมส่งข้อผิดพลาดผ่าน sink.addError AsyncSnapshot จะได้รับข้อผิดพลาด และ builder ควรแสดงข้อความที่เหมาะสมหรือ UI สำรอง
ได้ หาก Stream เป็น broadcast (สร้างผ่าน StreamController.broadcast) Stream แบบ single-subscription อนุญาตให้ผู้สมัครสมาชิกเพียงคนเดียว ในการแชร์สตรีมหนึ่งระหว่างหลายวิดเจ็ต ให้ใช้ตัวควบคุม broadcast หรือแพ็คเกจ rxdart กับ BehaviourSubject
ใช้พารามิเตอร์ buildWhen เพื่อกรองเหตุการณ์ที่ควรทำให้เกิดการสร้าง UI ใหม่ นอกจากนี้ให้ใช้ Stream.transformer หรือ Stream.where เพื่อกรองข้อมูลก่อนส่งไปยัง StreamBuilder
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม