เนื้อหาตอนต่อไปนี้ จะมาดูเกี่ยวกับการใช้งาน async widgets
ที่ชื่อว่า FutureBuilder เป็น widget ที่จะจัดการกับข้อมูลสุดท้าย
ที่จะได้รับในอนาคต เกี่ยวกับเรื่องนี้ เราต้องเข้าใจเกี่ยวกับรูปแบบ
การใช้งาน Asynchronous Programming ในภาษา Dart ทบทวน
ได้ที่บทความ
การใช้งาน Asynchronous Programming ในภาษา Dart เบื้องต้น http://niik.in/949
https://www.ninenik.com/content.php?arti_id=949 via @ninenik
*เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/961
จำลองข้อมูลในอนาคตด้วย Future
เราจะจำลองข้อมูลที่จะได้มาในอนาคต ความหมายที่จะสื่อก็คือ เช่น เราดึงข้อมูล
ผ่านเครือข่าย ก็จำเป็นจะต้องรอสักพักกว่าข้อมูลจะมา ก็เกิดเวลาที่ต้องรอขึ้น
ตัวอย่าง
// จำลองข้อมูลที่จะได้ หรือจะเกิดในอนาคต final Future<String> _calculation = Future<String>.delayed( const Duration(seconds: 2), () => 'Data Loaded', );
เรากำหนดตัวแปรชื่อ _calculation เป็นข้อมูล Future ที่มีชนิดข้อมูลเป็นข้อความ String โดยสร้าง
จากการกำหนดเวลาหน่วงการคืนค่าจากคำสั่ง Future<String>.delayed() พอครบ 2 วินาทีก็ให้คืนคำว่า
'Data Loaded' กลับมา เหตุผลที่เราต้องรู้จักกับข้อมูล Future ก็เพราะว่า ตัว FutureBuilder widget
ที่เราจะใช้งานในบทความนี้จำเป็นต้องใช้ข้อมูล Future นั่นเอง
ส่วนของไฟล์ home.dart ก่อนกำหนดใช้งาน FutureBuilder
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>[ Text('Home Screen'), ], ) ), ); } }
รูปแบบการใช้งาน FutureBuilder
FutureBuilder<String>( // กำหนดชนิดข้อมูล future: _calculation, // ข้อมูล Future //builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (context, snapshot) { // สร้าง widget เมื่อได้ค่า snapshot ข้อมูลสุดท้าย if (snapshot.hasData) { // ถ้าได้ค่าข้อมูลสุดท้าย return Text('Completed'); } else if (snapshot.hasError) { // ถ้ามี error return Text('${snapshot.error}'); } // ค่าเริ่มต้น, แสดงตัว Loading. return const CircularProgressIndicator(); }, ),
ส่วนแรกเป็นส่วนของการกำหนดชนิดของข้อมูล Future เราต้องกำหนดให้ถูกต้อง อย่างข้างตันเรา
กำหนดเป็น <String> มาจากข้อมูล Future<String> ถ้าเป็นข้อมูลอื่นเช่น List<String> เราก็จะ
กำหนดเป็น <List<String>> แบบนี้เป็นต้น
ส่วนที่สองเป็นข้อมูล future ก็ตัวแปรข้อมูล Future หรือจะเรียกฟังก์ชั่นมาใช้เลยก็ได้ ในตัวอย่างเรากำหนด
ค่าเป็น _calculation เป็นค่าที่เราจะใช้งาน
คำสั่ง builder จะรอทำคำสั่งสร้าง widget โดยอาศัยข้อมูลสุดท้ายที่เรียกว่า snapshot ที่ได้จากข้อมูล
Future เป็นข้อมูล AsyncSnapshot ซึ่งต้องรอข้อมูลหรือมีเวลาที่ต้องรอ จะได้ผลลัพธ์สุดท้าย
ในคำสั่ง builder เราก็จะ return Widget ที่ต้องการออกไปแสดง โดยมีเงื่อนไข ทำการตรวจสอบข้อมูล
snapshot ใน 3 รูปแบบ หรือจะเขียน 2 รูปแบบตามตัวอย่างด้านบนก็ได้ โดยไม่ต้องมี else ในเงื่อนไขสุดท้าย
คือถ้ามีข้อมูล ก็แสดงด้วย Text ข้อความคำว่า Completed ถ้ามี error ก็แสดงข้อความ error และถ้าไม่เข้า
เงื่อนไขทั้งสอง ก็แสดงตัว loading ด้วย CircularProgressIndicator widget
ตัวอย่างกรณี error ก็เช่น ในคำสั่งดึงข้อมูล Future เกิด Exception ขึ้น
// จำลองข้อมูลที่จะได้ หรือจะเกิดในอนาคต final Future<String> _calculation = Future<String>.delayed( const Duration(seconds: 5), () => throw Exception("ข้อมูลไม่พร้อมใช้งาน"), // 'Data Loaded', );
แทนที่จะส่งข้อความคำว่า 'Data Loaded' ก็เกิด error ขึ้นและส่งค่า Exception กลับมา ก็จะเข้าเงื่อนไขที่สอง
นอกจากเราจะใช้การตรวจสอบการมีข้อมูลของ snapshot ด้วยคำสั่ง snapshot.hasData ที่จะคืนค่าเป็น true / false
แล้ว เรายังสามารถใช้การตรวจสอบ สถานะการเชื่อมต่อจากค่าของ snapshot.connectionState ได้ โดยจะมีด้วยกัน
สองค่า คือ ConnectionState.waiting และ ConnectionState.done โดยเมื่อเริ่มต้นดึงข้อมูล Future ก็จะขึ้น waiting
ในครั้งแรก และเมื่อได้ข้อมูลกลับมา หรือเกิด error ก็จะขึ้น done
เราสามารถเขียนตรวจสอบแบบง่ายอย่างนี้ก็ได้
if (snapshot.connectionState == ConnectionState.done) { return Text('Completed'); } // ค่าเริ่มต้น, แสดงตัว Loading.สถานะ ConnectionState.waiting return const CircularProgressIndicator();
อย่างไรก็ตาม ควรใช้แบบวิธีแรกจะครอบคลุมการทำงานมากกว่า เพราะกรณีที่สอง หากมี error ก็ยังมีค่า
connectionState เป็น done เหมือนกัน หากไม่เขียนคำสั่งเพิ่มเพื่อตรวจสอบอีก ก็จะกลายเป็น ส่ง Text
ข้อความ 'Completed' ออกไป วิธีนี้จึงจะไม่ค่อยครอบคลุมเท่าไหร่
มาดูตัวอย่างโค้ด และผลลัพธ์การทำงาน ไฟล์ 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> { // จำลองข้อมูลที่จะได้ หรือจะเกิดในอนาคต final Future<String> _calculation = Future<String>.delayed( const Duration(seconds: 5), () => 'Data Loaded', // throw Exception("ข้อมูลไม่พร้อมใช้งาน") ); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ FutureBuilder<String>( // กำหนดชนิดข้อมูล future: _calculation, // ข้อมูล Future //builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (context, snapshot) { // สร้าง widget เมื่อได้ค่า snapshot ข้อมูลสุดท้าย if (snapshot.hasData) { // ถ้าได้ค่าข้อมูลสุดท้าย return Text('Completed'); } else if (snapshot.hasError) { // ถ้ามี error return Text('${snapshot.error}'); } // ค่าเริ่มต้น, แสดงตัว Loading.สถานะ ConnectionState.waiting return const CircularProgressIndicator(); }, ), ], ) ), ); } }
ผลลัพธ์
เริ่มต้นเราไปอยู่ที่หน้า Settings จากนั้นคลิกมาหน้า Home ที่เราใช้งาน FutureBuilder ตัว Loading
จะแสดงประมาณ 5 วินาทีตามค่าที่กำหนด แล้วขึ้นข้อความว่า Completed
เรามาจำลองการสร้างข้อมูล Future โดยใช้งานเป็นแบบฟังก์ชั่น ให้เสมือนกับเราจะดึงข้อมูลใน
ฝั่งของ server มาใช้งาน จะได้เป็นดังนี้
class _HomeState extends State<Home> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ FutureBuilder<String>( // กำหนดชนิดข้อมูล future: fetchData(), // ข้อมูล Future //builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (context, snapshot) { // สร้าง widget เมื่อได้ค่า snapshot ข้อมูลสุดท้าย if (snapshot.hasData) { // ถ้าได้ค่าข้อมูลสุดท้าย return Text('Completed'); } else if (snapshot.hasError) { // ถ้ามี error return Text('${snapshot.error}'); } // ค่าเริ่มต้น, แสดงตัว Loading.สถานะ ConnectionState.waiting return const CircularProgressIndicator(); }, ), ], ) ), ); } } // จำลองใช้เป็นแบบฟังก์ชั่น ให้เสมือนดึงข้อมูลจาก server Future<String> fetchData() async { final response = await Future<String>.delayed( const Duration(seconds: 5), () => 'Data Loaded', ); return response; }
เราสร้างฟังก์ชั่นชื่อว่า fetchData() แยกออกมา แล้วเรียกใช้ในส่วนของการกำหนดค่า future property
ของ FutureBuilder widget ผลลัพธ์ที่ได้ก็จะเหมือนกับวิธีแรก แต่ที่เราทำเพิ่มในรูปแบบนี้ก็พี่จำลองการทำงาน
ให้เสมือนการไปดึงข้อมูลในฝั่ง server
เนื้อหาตอนนี้ ถือได้ว่าเราได้ทำความเข้าใจเบื้องต้นกับการใช้งาน FutureBuilder widget เป็นการปูพื้นฐาน
สำหรับเนื้อหาในลำดับต่อๆ ไป รอติดตาม