ข้ามไปยังเนื้อหา

Futures & Streams in the UI

คุณ await ใน build ไม่ได้ — build ต้องเป็น synchronous และอาจรันหลายครั้ง Flutter เลยให้ widget สองตัวที่ subscribe แหล่งข้อมูล async แล้ว rebuild เมื่อมีค่าใหม่ปล่อยออกมา: FutureBuilder สำหรับ Future แบบ one-shot และ StreamBuilder สำหรับ Stream ที่มีหลายค่า

ทั้งคู่ส่ง AsyncSnapshot มาให้ builder ของคุณ ซึ่งบอก state ปัจจุบันของแหล่ง async: ยังรออยู่, มีข้อมูลแล้ว, หรือ error?

FutureBuilder<User>(
future: _userFuture, // Future ที่สร้าง OUTSIDE build (ดูกับดัก)
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator(); // loading
}
if (snapshot.hasError) {
return Text('Failed: ${snapshot.error}'); // error
}
final user = snapshot.data!; // ข้อมูลพร้อมแล้ว
return Text(user.name);
},
)

อ่าน snapshot ตามลำดับตายตัว: waiting → error → data การ handle ครบทั้งสาม (บวก empty state เมื่อจำเป็น) คือสิ่งที่แยกหน้าจอที่ทนทานออกจากหน้าที่แว้บ null error ชั่วขณะ

StreamBuilder มีรูปแบบเดียวกันสำหรับ Stream — rebuild ทุกครั้งที่ stream ปล่อยค่าใหม่ จึงเหมาะกับข้อมูล live (chat feed, location stream, Bloc state)

StreamBuilder<int>(
stream: _tickerStream,
initialData: 0,
builder: (context, snapshot) {
return Text('Count: ${snapshot.data}'); // rebuild ทุกค่าใหม่
},
)

นี่คือบั๊ก FutureBuilder ที่พบบ่อยที่สุด: สร้าง future ข้างใน build

// ❌ ผิด — future ตัวใหม่ถูกสร้างทุก rebuild
// request จึงยิงซ้ำ และ loading spinner กระพริบไม่จบ
Widget build(BuildContext context) {
return FutureBuilder(
future: fetchUser(42), // <-- สร้างใหม่ทุก build
builder: ...,
);
}
// ✅ ถูก — สร้างครั้งเดียวแล้วเก็บไว้ (ใน initState สำหรับ StatefulWidget)
late final Future<User> _userFuture;
@override
void initState() {
super.initState();
_userFuture = fetchUser(42); // สร้างครั้งเดียว
}

เพราะ build รันได้หลายสาเหตุ (parent rebuild, เปลี่ยน theme, keyboard เด้งขึ้น) future ที่สร้างตรงนั้นจึงถูกสร้างใหม่ทุกครั้ง — trigger งานซ้ำและ reset snapshot กลับเป็น waiting ให้สร้าง future ครั้งเดียวใน initState หรือใน state-management layer แล้วส่ง future ตัวเดิม เข้าไป

ทำไมถึง await ใน build ตรง ๆ ไม่ได้?
AsyncSnapshot บอกอะไรคุณ?
อะไรจะผิดพลาดเมื่อสร้าง Future ข้างใน build?
Future แบบ one-shot สำหรับ FutureBuilder ควรถูกสร้างที่ไหน?