Flutter: เพิ่ม Pull to Refresh ให้กับ ListView

Published on

Authored by Pete. Pittawat Taveekitworachai.


หนึ่งในรูปแบบการใช้งานที่เรามักจะเห็นได้บ่อยสำหรับการโหลดข้อมูลใหม่ ๆ จากฝั่งผู้ใช้งาน โดยเฉพาะบน Android หรือแอปพลิเคชันที่ทำตาม Material Guideline ก็คือการ Swipe to Refresh นั่นเอง โดยทั่วไปเรามักจะมี List ซึ่งเราสามารถดึงลงมาได้มากกว่าปกติจากบนสุดแล้วปล่อย โดยระหว่างนั้นก็จะมี Indicator มาหมุนโชว์ให้ดูว่ากำลังดาวน์โหลดข้อมูลใหม่ ๆ เมื่อเสร็จแล้วก็จะหายไปและมีข้อมูลใหม่มาโชว์ที่ List นั่นเอง

Flutter ซึ่งสนับสนุน Material Design แบบ 1st class ก็แน่นอนว่าได้เตรียม Widget มาให้เราใช้งานเช่นกัน โดย Widget ตัวนั้น ก็ชื่อ RefreshIndicator นั่นเอง


RefreshIndicator

RefreshIndicator เป็น Widget ที่เราสามารถนำไป Wrap Widget ใด ๆ ก็ตามที่สามารถ Scroll ได้ (Scrollable) และจะเพิ่มพฤติกรรมที่เมื่อเกิดการ Over Scroll (Scroll เกิน Content ที่มี) จะทำการ Trigger onRefresh และโชว์ Animation สวย ๆ ขึ้นมาเพื่อบอกให้ผู้ใช้งานรู้ว่าได้มีการ Trigger แล้ว โดย onRefresh นั้นคาดหวังฟังก์ชันที่ไม่รับข้อมูลนำเข้าแต่ข้อมูลส่งออกต้องเป็น Future<void> หรือก็คือไม่คาดหวังข้อมูลส่งออกแต่ฟังก์ชันนั้นต้องมีพฤติกรรมของ Future เนื่องจากโดยทั่วไปเมื่อเกิดการ Refresh ขึ้นเรามักเรียกใช้งานฟังก์ชันที่ต้องมีการดึงข้อมูลจาก Network หรือไฟล์ที่ใช้เวลานานนั่นเอง สำหรับวิธีที่ง่ายที่สุดก็คือการระบุให้ฟังก์ชันนั้นเป็น async ฟังก์ชัน โดยการเพิม่ Keyword async เข้าไปนั่นเอง

ตัวอย่างโค้ด

    // ...
    @override
    Widget build(BuildContext context) {
    	Future<void> onPullToRefresh() async {
    		await Future.delayed(Duration(milliseconds: 500));
    		setState(() {
    			if (colors[0] == colorForSwap1[0]) {
    				colors = colorForSwap2;
    			} else {
    				colors = colorForSwap1;
    			}
    		});
    	}
        
    	return Scaffold(
                // ...
                RefreshIndicator(
                    onRefresh: onPullToRefresh,
                    child: ListView.builder(
                        padding: const EdgeInsets.all(8),
                        itemCount: 3,
                        itemBuilder: (context, index) {
                            return Container(
                                height: 50,
                                color: colors[index],
                                child: Center(child: Text('$index')),
                            );
                        },
                    ),
                )
                // ...
    	);
    }
    // ...

จากโค้ดจะเห็นได้ว่าใน onPullToRefresh นั้นถูกใช้เพื่อเป็นฟังก์ชันสำหรับ onRefresh ใน RefreshIndicator โดยมี Future.delayed(...) เพื่อจำลองเวลาที่ใช้ในการดึงข้อมูลจาก Network ก่อนจะมีการเปลี่ยนแปลงข้อมูลใน List เป็นข้อมูลชุดใหม่นั่นเอง


Sidenote

สำหรับบางคนที่นำ RefreshIndicator ไปใช้แล้วไม่มีอะไรเกิดขึ้นทั้ง ๆ ที่ส่ง Argument ที่จำเป็นไปหมดแล้ว อาจเป็นไปได้ว่า Scrollable Widget ที่เป็น Child ของ RefreshIndicator นั้นไม่ได้รองรับการ Over Scroll เช่น กรณีที่ข้อมูลของเราอาจไม่เยอะจนเกิน Viewport ซึ่งเราสามารถแก้ไขได้โดยการ เพิ่ม Properties physics: **const** AlwaysScrollableScrollPhysics(), เข้าไปใน Child Widget ตัวนั้น (Scrollable) เพื่อการันตีการ Scroll ได้เสมอ

    // ...
    RefreshIndicator(
    	onRefresh: someFunction,
    	child: ListView(
    		physics: const AlwaysScrollableScrollPhysics(),
    		children: // ...
    	)
    ),
    // ...

สำหรับคนที่ต้องการตัวอย่างโค้ดแบบเต็ม สามารถตามไปดูได้ที่ GitHub


📚 Hope you enjoy reading! 📚