การใช้งาน StreamBuilder จัดการข้อมูล Stream ใน Flutter

บทความใหม่ เดือนที่แล้ว โดย Ninenik Narkdee
flutter streambuilder stream

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ flutter streambuilder stream



ในตอนที่แล้วเรารู้จักกับ FutureBuilder ซึ่งเป็น async widgets
หนึ่งใน Flutter เนื้อหานี้เรามาดูต่อกับอีกหนึ่ง async widgets ที่มี
รูปแบบการใช้งานคล้ายๆ กัน แต่ใช้กับข้อมูลที่เป้น Stream ชื่อว่า
StreamBuilder widget ใครยังไม่  เข้าใจเกี่ยวกับข้อมูล stream 
สามารถดูรายละเอียดเพิ่มเติมได้ที่ลิ้งค์ด้านล่างนี้
    ข้อมูล Stream การสร้าง และใช้งาน Stream ในภาษา Dart เบื้องต้น http://niik.in/962
 
    *เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/961
 
    *ควรอ่านเนื้อหาตอนที่แล้วเรื่อง FutureBuilder ก่อน
    การใช้งาน FutureBuilder ที่เป็น Async widgets ใน Flutter http://niik.in/1036
 
 

จำลองข้อมูล Stream

    เราจะจำลองข้อมูล stream สำหรับใช้ในการทดสอบ ข้อมูล stream เข้าใจอย่างง่าย
ก็คือข้อมูลที่มีการปล่อยออกมาอย่างต่อเนื่อง ในช่วงเวลาหนึ่ง หรือก็คือข้อมูล Future ที่มี
การส่งออกมาอย่างต่อเนื่อง ถ้าเปรียบเทียบกับข้อมูล Future ก็คือ 
 
ข้อมูล future 
    เราเรียกใช้ - รอข้อมูล - ข้อมูลส่งกลับมา จบขั้นตอน 
ข้อมูล stream 
    เราเรียกใช้ - รอข้อมูล - ข้อมูลส่งกลับมา - ใช้งานข้อมูล - ข้อมูลส่งกลับมา - ใช้งานข้อมูล
ข้อมูลลำดับต่อไปส่งกลับมาอีก เราใช้ข้อมูลต่อเนื่องไปเรื่อยๆ จนจบการ stream
 
 
// จำลองข้อมูล stream
final Stream<int> _bids = (() async* {
  await Future<void>.delayed(const Duration(seconds: 3));
  yield 1;
  await Future<void>.delayed(const Duration(seconds: 3));
  yield 2;
  await Future<void>.delayed(const Duration(seconds: 3));
  yield 3;
  await Future<void>.delayed(const Duration(seconds: 3));
})();
 
    เราจำลองข้อมูลตัวเลขการประมูลตัวแปรชื่อ  _bids เป็นข้อมูล Stream มีชนิดข้อมูลเป็น int ในการประมูล
แต่ละครั้ง เราสมมติเคาะราคาค่าประมูล 1 2 และ 3 ตามลำดับ โดยจำลองการหน่วงเวลาไว้ทุก 3 วินาที เสมือนว่า
ในทุกๆ 3 วินาทีในขณะกำลังประมูล ก็มีคนให้ราคาเพิ่มขึ้นเรื่อยๆ โดยคำสั่ง yield จะเป็นตัวทำหน้าที่ส่งค่าออก
มาจากข้อมูล stream การประมูลจะจบหรือสิ้นสุดหลังจากได้ค่าเป็น 3 แล้ว 3 วินาที
 

    รูปแบบการใช้งาน StreamBuilder

 
StreamBuilder<int>( // ชนิดข้อมูล Stream
  stream: _bids, // ข้อมูล Stream
  builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
    print("builder");// สำหรับทดสอบ
    print(snapshot.connectionState); // สำหรับทดสอบ
    List<Widget> children; // กำหนดตัวแปร สำหรับเก็บ widget ที่จะคืนค่ากลับ
    if (snapshot.hasError) { // กรณี error
          // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
          // children = <Widget>[];
    } else { // กรณีอื่นๆ 
      // ตรวจสอบค่าสถานะการเชื่อมต่อ แล้วทำคำสั่งตามเงื่อนไขนั้นๆ 
      switch (snapshot.connectionState) {
        case ConnectionState.none: // กรณีสถานะเป็น none
          // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
          // children = <Widget>[];
          break;
        case ConnectionState.waiting: // กรณีสถานะเป็น waiting
          // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
          // children = <Widget>[];
          break;
        case ConnectionState.active: // กรณีสถานะเป็น active
          // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
          // children = <Widget>[];
          break;
        case ConnectionState.done: // กรณีสถานะเป็น done
          // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
          // children = <Widget>[];
          break;
      }
    }

    // คืนค่าเป็นรูปแบบ widget ที่กำหนดจากตัวแปร children
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: children,
    );
  },
),
 
    รูปแบบการกำหนดใช้งาน StreamBuilder แทบจะเหมือนกับการใช้งาน FutureBuilder มีการกำหนด
ชนิดข้อมูล Stream กำหนดข้อมูล Stream ในส่วนของ stream property และมีการใช้งานคำสั่ง builder
เพื่อสร้าง widget กลับออกไปแสดง โดยอาศัยข้อมูล snapshot  แต่ใน StreamBuilder สิ่งที่เพิ่มเข้ามา
ก็คือสถานะของ ConnectionState.active ทั้งนี้ก็เพราะว่าข้อมูล stream มีการส่งออกมาต่อเนื่องตามลำดับ
จนกว่าจะสิ้นสุดข้อมูล ในขณะที่กรณีของข้อมูล future จะมีหลักๆ สองสถานะคือ waiting กับ done แต่ใน
ข้อมูล stream จะมี active เพิ่มเข้ามาด้วย  และสถานะ active ก็ไม่ได้มีแค่ครั้งเดียว แต่จะมีไปเรื่อยๆ จนกว่า
จะสิ้นสุดการ stream ข้อมูล ดังนั้น ในคำสั่ง builder ที่เราใส่คำสั่ง print("builder") ไว้ ก็จะทำงานทุกครั้ง
ที่มีข้อมูลใหม่เข้ามา นั่นก็คือ widget ส่วนนี้จะถูกสร้างใหม่ตามข้อมูลไปเรื่อยๆ
    การตรวจสอบเงื่อนไขของข้อมูล stream จึงใช้วิธีการดูค่า connectionState เป็นหลัก โดย 
    - waiting คือสถานะเริ่มต้นก่อนรับข้อมูล
    - active คือสถานะได้รับและกำลังใช้งานข้อมูล
    - done คือสิ้นสุดการ stream หรือสิ้นสุดข้อมูล stream แล้ว
    - none สำหรับสถานะนี้มีทั้งใน stream และ future แต่จะเกิดขึ้นเมื่อมีการเปลี่ยนข้อมูลจากแหล่งใหม่
 
    ตัวอย่างการกำหนดและใช้งานในไฟล์ home.dart
 
import 'package:flutter/material.dart';
  
class Home extends StatefulWidget {
    static const routeName = '/';
 
    const Home({Key? key}) : super(key: key);
  
    @override
    State<StatefulWidget> createState() {
        return _HomeState();
    }
}
  
class _HomeState extends State<Home> {

    // จำลองข้อมูล stream
    final Stream<int> _bids = (() async* {
      await Future<void>.delayed(const Duration(seconds: 3));
      yield 1;
      await Future<void>.delayed(const Duration(seconds: 3));
      yield 2;
      // throw Exception('Intentional exception');
      await Future<void>.delayed(const Duration(seconds: 3));
      yield 3;
      await Future<void>.delayed(const Duration(seconds: 3));
    })();  
 
    @override
    Widget build(BuildContext context) {
        print("build");// สำหรับทดสอบ
        return Scaffold(
            appBar: AppBar(
                title: Text('Home'),
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      StreamBuilder<int>( // ชนิดข้อมูล Stream
                        stream: _bids, // ข้อมูล Stream
                        builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                          print("builder");// สำหรับทดสอบ
                          print(snapshot.connectionState); // สำหรับทดสอบ
                          List<Widget> children; // กำหนดตัวแปร สำหรับเก็บ widget ที่จะคืนค่ากลับ
                          if (snapshot.hasError) { // กรณี error
                            children = <Widget>[
                              const Icon(
                                Icons.error_outline,
                                color: Colors.red,
                                size: 60,
                              ),
                              Padding(
                                padding: const EdgeInsets.only(top: 16),
                                child: Text('Error: ${snapshot.error}'),
                              ),
                              Padding(
                                padding: const EdgeInsets.only(top: 8),
                                child: Text('Stack trace: ${snapshot.stackTrace}'),
                              ),
                            ];
                          } else { // กรณีอื่นๆ 
                            // ตรวจสอบค่าสถานะการเชื่อมต่อ แล้วทำคำสั่งตามเงื่อนไขนั้นๆ 
                            switch (snapshot.connectionState) {
                              case ConnectionState.none: // กรณีสถานะเป็น none
                                // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
                                children = const <Widget>[
                                  Icon(
                                    Icons.info,
                                    color: Colors.blue,
                                    size: 60,
                                  ),
                                  Padding(
                                    padding: EdgeInsets.only(top: 16),
                                    child: Text('Select a lot'),
                                  )
                                ];
                                break;
                              case ConnectionState.waiting: // กรณีสถานะเป็น waiting
                                // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
                                children = const <Widget>[
                                  SizedBox(
                                    child: CircularProgressIndicator(),
                                    width: 60,
                                    height: 60,
                                  ),
                                  Padding(
                                    padding: EdgeInsets.only(top: 16),
                                    child: Text('Awaiting bids...'),
                                  )
                                ];
                                break;
                              case ConnectionState.active: // กรณีสถานะเป็น active
                                // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
                                children = <Widget>[
                                  const Icon(
                                    Icons.check_circle_outline,
                                    color: Colors.green,
                                    size: 60,
                                  ),
                                  Padding(
                                    padding: const EdgeInsets.only(top: 16),
                                    child: Text('\$${snapshot.data}'),
                                  )
                                ];
                                break;
                              case ConnectionState.done: // กรณีสถานะเป็น done
                                // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
                                children = <Widget>[
                                  const Icon(
                                    Icons.info,
                                    color: Colors.blue,
                                    size: 60,
                                  ),
                                  Padding(
                                    padding: const EdgeInsets.only(top: 16),
                                    child: Text('\$${snapshot.data} (closed)'),
                                  )
                                ];
                                break;
                            }
                          }

                          // คืนค่าเป็นรูปแบบ widget ที่กำหนดจากตัวแปร children
                          return Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: children,
                          );
                        },
                      ),
                    ],
                )
            ),
        );
    }
}  
 
    ผลลัพธ์ที่ได้
 
 


 
 
    เราเริ่มต้นอยู่ที่หน้า profile จากนั้นกดมาที่หน้า home ที่มีการใช้งาน StreamBuilder เมื่อเข้ามาก็จะเกิด
waiting ขึ้นเริ่มการ stream ข้อมูล พอมีข้อมูล 1 2 และ 3 ค่อยๆ ถูกส่งมา ก็จะเข้าสู่ active จำนวนเงินที่ทำ
การประมูลก็เพิ่มขึ้นตามค่า ตัว builder ก็ทำการสร้าง widget ใหม่ทุกครั้งที่ข้อมูลในสถานะ active กำลังทำงาน
หลังจากได้ค่าจำนวนเลข 3 แล้ว 3 วินาที ก็ไม่มีการส่งข้อมูลมาอีก สิ้นสุดการประมูล เข้าสถานะ done ข้อมูลก็จะ
แสดง เคาะที่ราคา $3 ขึ้นข้อความปิดการประมูล
 
    เช่นกัน เราสามารถสร้างเป็นฟังก์ชั่นแล้วเรียกใช้งานดังนี้ได้
 
class _HomeState extends State<Home> {

    @override
    Widget build(BuildContext context) {
        print("build");// สำหรับทดสอบ
        return Scaffold(
            appBar: AppBar(
                title: Text('Home'),
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      StreamBuilder<int>( // ชนิดข้อมูล Stream
                        stream: _bidStream(), // ข้อมูล Stream
                        builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                          print("builder");// สำหรับทดสอบ
                          print(snapshot.connectionState); // สำหรับทดสอบ
                          List<Widget> children; // กำหนดตัวแปร สำหรับเก็บ widget ที่จะคืนค่ากลับ
                          if (snapshot.hasError) { // กรณี error
                            children = <Widget>[
                              const Icon(
                                Icons.error_outline,
                                color: Colors.red,
                                size: 60,
                              ),
                              Padding(
                                padding: const EdgeInsets.only(top: 16),
                                child: Text('Error: ${snapshot.error}'),
                              ),
                              Padding(
                                padding: const EdgeInsets.only(top: 8),
                                child: Text('Stack trace: ${snapshot.stackTrace}'),
                              ),
                            ];
                          } else { // กรณีอื่นๆ 
                            // ตรวจสอบค่าสถานะการเชื่อมต่อ แล้วทำคำสั่งตามเงื่อนไขนั้นๆ 
                            switch (snapshot.connectionState) {
                              case ConnectionState.none: // กรณีสถานะเป็น none
                                // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
                                children = const <Widget>[
                                  Icon(
                                    Icons.info,
                                    color: Colors.blue,
                                    size: 60,
                                  ),
                                  Padding(
                                    padding: EdgeInsets.only(top: 16),
                                    child: Text('Select a lot'),
                                  )
                                ];
                                break;
                              case ConnectionState.waiting: // กรณีสถานะเป็น waiting
                                // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
                                children = const <Widget>[
                                  SizedBox(
                                    child: CircularProgressIndicator(),
                                    width: 60,
                                    height: 60,
                                  ),
                                  Padding(
                                    padding: EdgeInsets.only(top: 16),
                                    child: Text('Awaiting bids...'),
                                  )
                                ];
                                break;
                              case ConnectionState.active: // กรณีสถานะเป็น active
                                // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
                                children = <Widget>[
                                  const Icon(
                                    Icons.check_circle_outline,
                                    color: Colors.green,
                                    size: 60,
                                  ),
                                  Padding(
                                    padding: const EdgeInsets.only(top: 16),
                                    child: Text('\$${snapshot.data}'),
                                  )
                                ];
                                break;
                              case ConnectionState.done: // กรณีสถานะเป็น done
                                // สร้าง widget สำหรับกรณีนี้ไว้ในตัวแปร children
                                children = <Widget>[
                                  const Icon(
                                    Icons.info,
                                    color: Colors.blue,
                                    size: 60,
                                  ),
                                  Padding(
                                    padding: const EdgeInsets.only(top: 16),
                                    child: Text('\$${snapshot.data} (closed)'),
                                  )
                                ];
                                break;
                            }
                          }

                          // คืนค่าเป็นรูปแบบ widget ที่กำหนดจากตัวแปร children
                          return Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: children,
                          );
                        },
                      ),
                    ],
                )
            ),
        );
    }
}  

// จำลองใช้เป็นแบบฟังก์ชั่น ให้เสมือนดึงข้อมูลจาก server
Stream<int> _bidStream() async* {
  await Future<void>.delayed(const Duration(seconds: 3));
  yield 1;
  await Future<void>.delayed(const Duration(seconds: 3));
  yield 2;
  // throw Exception('Intentional exception');
  await Future<void>.delayed(const Duration(seconds: 3));
  yield 3;
  await Future<void>.delayed(const Duration(seconds: 3));
} 
 
เราสร้างฟังก์ชั่นชื่อว่า _bidStream() แยกออกมา แล้วเรียกใช้ในส่วนของการกำหนดค่า stream property
ของ StreamBuilder widget ผลลัพธ์ที่ได้ก็จะเหมือนกับวิธีแรก สังเกตเพิ่มเติมในส่วนของการกำหนด async
ให้กับกฟังก์ชั่น สำหรับข้อมูล stream เราจะใช้เป็น async*
 
    หวังว่าข้อมูลในเนื้อหานี้จะเป็นประโยชน์สำหรับทำความเข้าใจ และนำไปปรับประยุกต์ใช้งานต่อไป เนื้อหา
ตอนหน้าจะเป็นอะไร รอติดตาม


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ