Flutter ว่ากันด้วยเรื่อง Push, Push, Pop, Pop…Pop, Pop, Push, Push
Flutter ฉบับใช้จริงต้องรู้อะไรบ้าง
หลังจากที่เขียนบทความแรกไป ได้ความสนใจค่อนข้างดีพอสมควร ใครยังไม่อ่านหรือบุ๊คมาร์คเก็บไว้สามารถเข้าไปอ่านได้ที่ สรุป 10 อันดับ Flutter Layout ที่คุณควรรู้ ฉบับรวบรัด หลังจากบทความก่อนเราสรุปในเรื่องของ Layout คราวนี้เราจะมาสรุปอีกส่วนที่มีความสำคัญกับการเขียน Flutter ในการใช้งานจริงเช่นกัน
สำหรับการใช้งานจริงนั้น อีกส่วนที่มีความสำคัญไม่แพ้กันกับเรื่อง Layout นั่นคือ เรื่องการเปลี่ยนหน้า หรือการทำ Routing ในบทความนี้หลักๆจะอธิบายถึงคอนเซ็ปต์การใช้งานของแต่ละฟังก์ชั่นว่ามีแนวทางการใช้งานอย่างไรบ้าง
ปูพื้นฐานเล็กน้อย
ก่อนที่จะเริ่มกันสำหรับ Flutter นั้นการเปลี่ยนหน้าแสดงผลจากหน้าหนึ่งไปยังอีกหน้าหนึ่ง สามารถเปลี่ยนได้อย่างง่ายดายโดยใช้ class
ที่ชื่อว่า Navigator คอนเซ็ปต์ของการเปลี่ยนหน้าของแอปพลิเคชันนั้นคือการ Stack หรือการซ้อนทับ หน้าจอแต่ละหน้าเอาไว้ซ้อนๆกันไปเรื่อยๆ ดังนั้นคอนเซ็ปของการเปลี่ยนหน้าใน Flutter นั้นคือ ใช้ push ในการเพิ่มหน้าใหม่ขึ้นมาซ้อนทับ ส่วน pop คือลบหน้าปัจจุบันออกจากการซ้อนทับออกไปนั่นเอง
นอกจากเรื่อง push และ pop แล้ว เรื่องการทำ Routing ใน Flutter คล้ายๆแบบ REST API เพื่อให้จัดการได้ง่ายขึ้นสามารถทำได้ด้วยเช่นกัน เช่น เวลาต้องการตั้งให้เวลาเรียก '/'
จะนำไปหน้า HomeScreen หรือ ต้องการตั้ง '/order'
จะนำไปหน้า OrderScreen วิธีการประกาศง่ายๆคือ
MaterialApp(
initialRoute: '/',
routes: <String, WidgetBuilder> {
'/': (BuildContext context) => Home(),
'/order': (BuildContext context) => Order(),
'/screen1' : (BuildContext context) => Screen1(),
'/screen2' : (BuildContext context) => Screen2(),
'/screen3' : (BuildContext context) => Screen3(),
'/screen4' : (BuildContext context) => Screen4()
},
)
นอกจากนั้นยังสามารถประกาศหน้าเริ่มต้น หรือ initialRoute ได้ด้วยเช่นกัน
เรื่องของ Push
อย่างที่ได้เกริ่นไปว่าใช้ push ในการเพิ่มหน้าใหม่ขึ้นมาซ้อนทับ หากเราเปิดมาหน้าแรกคือหน้า Screen1 และต้องการให้กดปุ่มแล้วไปที่หน้า Screen2 เราสามารถทำได้ดังนี้
push
RaisedButton(
onPressed:() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Screen2()),
);
},
child: Text("Go to Screen 2"),
),
pushName
RaisedButton(
onPressed:() {
Navigator.of(context).pushNamed('/screen2');
},
child: Text("Go to Screen 2"),
),
pushReplacement
กรณี pushReplacement นั้นใช้กับกรณีที่ว่าต้องการ push หน้าใหม่ขึ้นมาแทนที่หน้าปัจจุบัน
กรณีตัวอย่าง: หากเราต้องการทำแอปที่ออกแบบว่า เมื่อเราเข้าหน้าแรกเป็นหน้า Log in แล้วเมื่อพอ Log in เสร็จแล้วให้เข้าหน้า Home โดยที่เราอยากให้หน้าซ้อนอยู่ล่างสุดของ Stack เป็นหน้า Home แทนที่หน้า Log in ไปเลย เพราะเราไม่อยากให้กด Back กลับมาแล้วเป็นหน้า Log in หรือ ไม่อยากเก็บ State ของหน้า Log in ไว้ สามารถใช้ pushReplacement ได้เลย
RaisedButton(
onPressed:() {
Navigator.of(context).pushReplacementNamed(context, '/home');
},
child: Text("Log in"),
),
pushAndRemoveUntil
pushAndRemoveUntil ใช้ในกรณีที่เราต้องการ push หน้าใหม่ขึ้นมาแล้วทำการ ลบหน้าที่ซ้อนๆกันอยู่ด้านล่างของ Stack ทิ้งออกไป เวลากลับจะได้กลับไปหน้าที่ต้องการได้เลย
กรณีตัวอย่าง: หากเราต้องการทำแอปสั่งสินค้าออนไลน์ที่ออกแบบว่า เมื่อต้องการสั่งสินค้าและเราอยู่หน้าแรก (Home) เราจะทำการหยิบสินค้า และกดเข้าไปดูที่หน้าตะกร้าสินค้า (Cart) แล้วทำการกดสรุปรายการสั่งซื้อ (Order Summary) แล้วทำการกดยืนยันเป็นขั้นตอนสุดท้าย จากนั้นแอปจะนำไปที่หน้ารายละเอียดรายการสั่งซื้อที่สำเร็จแล้ว (Order Details) และเมื่อกด Back แอปจะนำกลับไปที่หน้าแรก (Home) ได้เลยโดยไม่ต้องผ่านหน้า Order Summary และ Cart ก่อน
RaisedButton(
onPressed:() {
Navigator.pushNamedAndRemoveUntil(context, '/order_details', ModalRoute.withName('/home'));
},
child: Text("Confirm"),
),
เรื่องของ Pop
pop นั้นใช้เมื่อที่เราต้องการกลับไปหน้าที่เคยผ่านมาแล้ว หรือหน้าที่ซ้อนอยู่ใต้หน้าปัจจุบัน หากเราปัจจุบันเรา push จนมาอยู่ที่หน้า Screen2 แล้วต้องการกดเพื่อกลับไปที่หน้า Screen1 สามารถทำได้ดังนี้
RaisedButton(
onPressed:() {
Navigator.pop(context);
},
child: Text("Back"),
),
canPop
กรณีที่เราต้องการรู้ว่าหน้าที่เราอยู่ปัจจุบันนั้นเป็นหน้าสุดท้าย หรือหน้าล่างสุดของ Stack หรือยัง โดย false
คือ ล่างสุดแล้วเพราะไม่สามารถ pop ได้อีกแล้ว และ true
คือยังไม่ล่างสุดเพราะยังสามารถ pop ได้นั่นเอง
bool canPop = Navigator.canPop(context);
maybePop
ตัวช่วยสำหรับกันการ pop จนหน้าหมดหน้ามืด โดยไม่รู้ตัว กรณีที่เราต้องการที่จะ pop แล้วบังเอิญว่าเรามีการใช้คำสั่ง pop ในหลายๆที่พร้อมกัน อย่างเช่นการ pop Dialog
หรือ Modal
maybePop จะทำงานโดยการเช็คว่าตอนจะ pop นั้นหน้าที่เราอยู่ปัจจุบันนั้นเป็นหน้าสุดท้าย หรือหน้าล่างสุดของ Stack หรือยัง หากเช็คได้ว่าอยู่หน้าสุดท้ายแล้ว จะไม่ทำการ pop อีกเพราะจะทำให้แอปกลายเป็นหน้าจอสีดำว่างเปล่าสู่ความเวิ้งว้างอันไกลโพ้น หรือหน้าที่ Stack อยู่หมดไปนั่นเอง
RaisedButton(
onPressed:() {
Navigator.maybePop(context);
},
child: Text("Back"),
),
popUntil
กรณี popUntil นั้นใช้กับกรณีที่ว่าต้องการ pop หน้าทิ้งจนถึงหน้าที่ต้องการ
กรณีตัวอย่าง: กรณีเมื่อต้องการโพสต์รูปในแอป Social Media แบบ Instragram เราจะทำการกดสร้างโพสต์รูปในหน้าหลัก (Home) แอปจะนำไปหน้าเลือกรูป (Picker) จากนั้นจะไปหน้าแต่งรูป (Decoration) จากนั้นทำการกดโพสต์ สุดท้ายเมื่อโพสต์เสร็จ แอปจะนำกลับไปที่หน้าหลักโดย pop หน้าที่ผ่านมาทิ้งไปทั้งหมดจนถึงหน้าหลัก เพื่อเห็นโพสต์ที่เราเพิ่งจะโพสต์สำเร็จไป
RaisedButton(
onPressed:() {
Navigator.popUntil(context, ModalRoute.withName('/home'));
},
child: Text("Submit"),
),
popAndPushNamed
popAndPushNamed นั้นใช้กับกรณีที่ว่าต้องการ pop หน้าปัจจุบัน และทำการ push หน้าขึ้นมาใหม่แทน
กรณีตัวอย่าง: หากเราต้องการทำแอปสั่งสินค้าออนไลน์ที่ออกแบบว่า เมื่อเราเข้าหน้ารายการสินค้าทั้งหมด (Products) แล้วต้องการ filter สินค้าตามเงื่อนไขในหน้า (Conditions) เมื่อใส่เงื่อนไขเสร็จแล้วกดตกลง แอปจะนำไปที่หน้าสินค้าทั้งหมดพร้อมกับ filter ตามเงื่อนไขที่ส่งมา
RaisedButton(
onPressed:() {
Navigator.popAndPushNamed(context, '/products');
},
child: Text("OK"),
),
แต่เดี๋ยวนะ !!
แล้ว popAndPushNamed ต่างกับ pushReplacementNamed ยังไง?
เพราะทั้งคู่คือการแทนที่หน้าเดิมทั้งคู่ คำตอบที่ดีที่สุดเวลาที่ต้องเลือกใช้ คือ ต่างกันที่ animation ยังไงล่ะ popAndPushNamed จะเป็น animation แบบ pop ไปที่หน้าใหม่ ส่วน pushReplacementNamed จะเป็น animation แบบ push ไปที่หน้าใหม่
แล้วเรื่องของการส่งข้อมูลล่ะ
ในการใช้งานจริงเราจะต้องมีการส่งข้อมูลระหว่างหน้าไปมาหากัน เช่น แอปสั่งอาหารเวลาเรากดเลือกร้าน เราต้องส่งข้อมูล id ของร้านเข้าไปในหน้า Menu List ที่ถูก push ขึ้นมาด้วย เป็นต้น
กรณีไม่มีการประกาศ Route
เมื่อเราจะส่ง title จาก Screen1 ไปยัง Screen2 หากไม่มีการประกาศ Route จะสามารถส่งผ่าน class Screen2 ได้เลย
Screen1 สามารถส่งข้อมูลไปได้ดังนี้
RaisedButton(
onPressed:() {
Sring title = "From Screen 1"
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Screen2(title)),
);
},
child: Text("Go to Screen 2"),
),
และ Screen2 สามารถรับข้อมูลได้โดย
class Screen2 extends StatelessWidget {
final String title;
Screen2(this.title); @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
);
}
}
กรณีมีการประกาศ Route
เมื่อเราจะส่ง title จาก Screen1 ไปยัง Screen2 หากมีการประกาศ Route จะสามารถส่งผ่านตัวแปร arguments แทน
Screen1 สามารถส่งข้อมูลไปได้ดังนี้
RaisedButton(
onPressed:() {
Sring title = "From Screen 1"
Navigator.pushNamed(context, '/screen2', arguments: title);
},
child: Text("Go to Screen 2"),
),
และ Screen2 สามารถรับข้อมูลได้โดย
class Screen2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String title = ModalRoute.of(context).settings.arguments; return Scaffold(
appBar: AppBar(title: Text(title)),
);
}
}
ส่งข้อมูลกลับมาก็ทำได้นะเออ
เมื่อเราจะส่ง title กลับจาก Screen2 ไปยัง Screen1 ก็สามารถทำได้เช่นกัน
Screen2 สามารถส่งข้อมูลกลับไปได้ดังนี้
RaisedButton(
onPressed:() {
Sring title = "From Screen 2"
Navigator.pop(context, title);
},
child: Text("Back to Screen 1"),
),
และ Screen1 สามารถรับข้อมูลได้โดย
RaisedButton(
onPressed:() async {
String title = await Navigator.pushNamed(context, '/screen2');
print(title);
},
child: Text("Go to Screen 2"),
),
เนี่ยล่ะครับ ว่ากันด้วยเรื่องของ push และ pop ใน Flutter ไปลองทำกันได้เลย ส่วนตัวผมคิดว่าใช้งานสะดวกพอสมควรหากปรับตัวจนชินแล้ว
หากผมเขียนข้อมูลผิดพลาดประการใด หรือมีส่วนใดต้องการให้เสริมหรือให้คำแนะนำเพิ่มเติม สามารถแนะนำเข้ามาได้เลย
หากบทความนี้ถูกใจหรือเป็นประโยชน์ กดปรบมือเป็นกำลังใจให้ผมด้วยแล้วกันนะครับ ขอบคุณครับ