การใช้งาน Navigator และ Routing ใน Flutter เบื้องต้น

เขียนเมื่อ 5 ปีก่อน โดย Ninenik Narkdee
routing flutter navigator snackbar

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

ดูแล้ว 21,687 ครั้ง




ก่อนที่เราจะเข้าสู่เนื้อหาการประยุกต์ต่อจากตอนที่แล้ว
เนื่้องจาก สิ่งที่เราจะทำ คือการเปลี่ยนไปหน้าใหม่ใน App
ดังนั้นจำเป็นต้องเข้าใจ Navigator Widget ก่อน
    ทบทวนเนื้อหาตอนที่แล้วได้ที่
    การกำหนด และใช้งาน ListView Widget ใน Flutter เบื้องต้น http://niik.in/957
 

 

Navigator Widget คืออะไร

    Navigator เป็น widget ที่ใช้สำหรับจัดการกลุ่มของ widget ย่อย
ในรูปแบบที่วางซ้อนทับกันอย่างเป็นระเบียบ เข้าใจอย่างง่าย เหมือนมีแผ่น
กระดาษหลายแผ่นวางซ้อนกัน เรียกว่า stack โดยแผ่นแรกอยู่ล่างสุด แล้ว
เพิ่มแผ่นใหม่ซ้อนทับด้านบนเรื่อยๆ ซึ่งกระดาษแต่ละแผ่นก็คือ widget ย่อยๆ นั่นเอง
    ใน App ส่วนใหญ่แล้วจะใช้ Navigator ในลำดับต้นๆ ของโครงสร้างลำดับขั้น
ของ widget   เพื่อสำหรับจัดการประวัติการใช้งานหน้า screen หรือ page ในรูปแบบ
ของการวางซ้อนทับกัน โดยหน้าปัจจุบันก็จะอยู่ด้านบน และซ้อนทับหน้าอื่นที่เปิดมาก่อนหน้า
Navigator ใช้รูปแบบการวางซ้อนทับ และดึงออก ในการเปลี่ยนหน้า screen หรือ page ของ App
    ยกตัวอย่างเช่น เมื่อเปิด App ครั้งแรก หน้าหลักที่แสดง หรือก็คือหน้าแรก จะถูกวางลงไปใน
stack ก่อน ถ้ามีการเชื่อมโยงไปหน้าที่สอง หน้าที่สองก็จะถูกนำมาวางทับไว้ด้านบนของหน้าแรก
อีกที และถ้าหากต้องการกลับไปหน้าแรก เราก็ทำการดึงเอาหน้าที่สองที่อยู่ด้านบนออก ก็จะเหลือ
หน้าแรกที่แสดงอยู่ด้านล่าง หลักการก็จะประมาณนี้ ซึ่งถ้าเราคุ้นกับ dialog หรือหน้าแจ้งเตือนที่ชอบ
แสดงอยู่ด้านบนของหน้าที่ใช้งานอยู่ ก็จะเป็นรูปแบบเดียวกัน
 

 

การใช้งาน Navigator Widget

    ก่อนลงรายละเอียด ให้เข้าใจตรงกันก่อนว่า ใน Flutter เราจะเรียกหน้าที่ข้อมูลแบบเต็มจอว่า "route" ซึ่งเราอาจจะ
คุันเคยกับคำว่า "screen" หรือ "page" อย่างเช่น firstScreen หรือ Page 1 เหล่านี้คือความหมายเดียวกันกับ "route" ใน
Flutter และมีการจัดการ การทำงานด้วย Navigator widget 
    Navigator จะจัดการ stack (การเรียงซ้อนกันเป็นชั้นๆ) ของ Route object โดยใช้คำสั่ง Navigator.push() สำหรับ
เพิ่มรายการ route ใหม่เข้ามา และใช้คำสั่ง Navigator.pop() สำหรับเอา route ด้านบนสุดออก
    
    ข้อดีของการใช้งาน Navigator และ Route คือ เมื่อเราอยู่หน้าจอที่แสดงข้อมูลเต็มพื้นที่ และต้องการกลับไปยังหน้า
จอก่อนหน้า อย่างในมือถือ Android จะมีปุ่ม "back" ที่เป็นของระบบ "System UI" ของแต่ละเจ้า ซึ่งเป็นส่วนที่ไม่ได้อยู่ใน
พื้นที่ของหน้า App  โดยเราสามารถกด เพื่อกลับไปยังหน้าก่อนหน้าได้ โดยปุ่ม "back" นี้ก็จะทำการ pop() หน้าจอ
ด้านบนสุดออกไปให้เรา โดยที่ใน App อาจจะไม่จำเป็นต้องมีปุ่มเพื่อเรียกใช้คำสั่งนี้ก็ได้   แต่ในบางกรณีเช่นมือถือที่ไม่มีปุ่ม
"back" ด้านนอก หรือแม้แต่ใน iPhone ซึ่งปกติจะมีการใช้งาน AppBar การใช้งาน หากทำการเปิดหน้าใหม่ ก็จะมีปุ่ม "back"
เพิ่มเข้าไปใน AppBar อัตโนมัติ เพื่อกลับไปยังหน้าก่อนหน้า หรือก็คือเพื่อทำการ pop() หน้าบนสุดออกไป ซึ่งเป็นคุณสมบัติ
ของ Navigator widget  หรือท้ายสุดแล้ว หน้าที่แสดงไม่มีการใช้งาน AppBar เราก็ยังสามารถสร้างปุ่ม "back" หรือปุ่มอะไรก็
ได้เพื่อเรียกใช้คำสั่ง pop() เพื่อกลับไปหน้าก่อนหน้าได้
 
 

    การแสดง Route แบบเต็มจอ

    ถ้าเราสังเกตการใช้งานการสร้าง App แต่เริ่มต้น เราจะเห็นการใช้งาน MaterialApp widget ที่กำหนด home property สำหรับ
เป็น route เริ่มต้น เมื่อ App เริ่มทำงานตามคำสั่งโค้ดด้านล่าง ก็จะทำการเพิ่ม route หน้าแรกไปอยู่ด้านล่างสุดของ Navigator stack
 
void main() {
  runApp(MaterialApp(home: MyAppHome()));
}
    หากต้องการแสดงหน้าใหม่เพิ่มเข้ามา ก็ให้ทำการสร้าง route โดยใช้ MaterialPageRoute object เรียกใช้งานฟังก์ชั่น builder 
สร้างหน้าจอแสดงผลตามต้องการให้กับ route อีกที ดูโค้ดด้านล่างเป็นแนวทาง
 
Navigator.push(context, MaterialPageRoute<void>(
  builder: (BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('My Page')),
      body: Center(
        child: FlatButton(
          child: Text('POP'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  },
));
    จากโค้ด การเพิ่ม route เข้าไปใน Navigator เราใช้คำสั่ง Navigator.push(context, [route]); โดยในส่วนของ route เราก็ใช้
MaterialPageRoute สร้างหน้าตาของ App ที่ต้องการ ตามโค้ด้านบน 
    และจากโค้ดด้านบน เราก็จะเห็นว่าในหน้าใหม่ ที่เพิ่มเข้ามา มีการสร้างปุ่มอย่างง่าย พร้อมกำหนดการเรียกใช้งาน การกลับไปยังหน้า
ก่อนหน้า โดยใช้คำสั่ง 
 
Navigator.pop(context);
    อย่างไรก็ตาม อย่างที่กล่าวไปแล้วว่า หากมีการใช้งาน AppBar เราอาจจะไม่จำเป็นต้องกำหนดปุ่ม "back" เพิ่มเติม เพราะ หากมีการ
ใช้งาน Scaffold ที่มีการกำหนด AppBar ปุ่ม "back" จะถูกเพิ่มเข้ามาให้อัตโนมัติ
 
 

    การกำหนดชื่อให้กับ Route ใน Navigator

    ในการใช้งาน route ที่มีจำนวนมากๆ ใน App จะใช้วิธีกำหนดเป็นชื่อ เพื่อให้สะดวกในการเรียกใช้งาน โดยใช้รูปแบบโครงสร้าง path
เช่น '/a/b/c' โดยหน้าแรกที่ใช้ home property จะมี path เป็น '/' เป็นค่าเริ่มต้น
    MaterialApp สามารถสร้าง Route ให้กับ Navigator โดยกำหนด routes เป็น Map<String, WidgetBuilder> ถึงตรงนี้ ใครไม่รู้จักว่า
Map คืออะไรให้ศึกษา และทำความเข้าเกี่ยวกับ ภาษา Dart เพิ่มเติม ซึ่งเรามีเนื้อหาบทความ ภาษา Dart ปูพื้นฐานก่อนใช้งาน Flutter
    โดยเรากำหนดค่าแรกเป็น String ชื่อ path ของเรา route ที่ต้องการ และ WidgetBuilder เป็นฟังก์ช่ันสำหรับสร้างหน้า app ให้กับ
path นั้นๆ ดูตัวอย่างโค้ดด้านล่าง ประกอบ
 
void main() {
  runApp(MaterialApp(
    home: MyAppHome(), // จะมีชื่อ route เป็น '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => MyPage(title: 'page A'),
      '/b': (BuildContext context) => MyPage(title: 'page B'),
      '/c': (BuildContext context) => MyPage(title: 'page C'),
    },
  ));
}
    หากเราอยู่หน้าหลัก และต้องการไปที่หน้า route เท่ากับ "/c" ก็เรียกใช้คำสั่ง Navigator.pushNamed() ดังนี้
 
Navigator.pushNamed(context, '/c');
    รูปแบบการอ้างอิงชื่อ route ทำให้เราเรียกใช้งานได้ง่าย และสะดวกยิ่งขึ้น
 
 

    การส่งค่ากลับจาก Route

    กรณีที่เรามีการใช้งาน route เพื่อรับค่าบางอย่างจากผู้ใช้ เราสามารถเพิ่ม ค่าที่ต้องการส่งกลับมาในฟังก์ชั่น pop() โดยกำหนด
เป็น result parameter 
    และในขั้นตอนการเปิดหน้า route ที่ใช้สำหรับรับค่าจากผู้ใช้ที่เรีรยกใช้งาน Navigator.push() จะคืนค่าเป็น Future หรือคือค่า
ที่มีเรื่องเวลาที่ต้องรอเข้ามาเกี่ยวข้อง ดังนั้นในขั้นตอนการเปิดหน้ารับข้อมูล จะใช้ await keyword เพื่อให้รองรับการใช้งานแบบ 
asynchronous นั่นคือ ให้รอค่าจากผู้ใช้เพื่อให้ได้ค่าสุดท้าย ก่อนนำไปใช้งาน
    ดูตัวอย่างการกำหนดใช้งานด้านล่าง สมมติเราต้องการบอกให้ผู้ใช้กดปุ่ม "OK" เพื่อยืนยันการดำเนินการใดๆ ต่อ ดังนั้นเราต้องรอ
โดยกำหนด await keyword ให้กับคำสั่ง Navigator.push() เพื่อรอผลลัพธ์สุดท้ายจากหน้าที่เปิดขึ้นมา ที่จถถูกส่งกลับมาตอนเรียก
ใช้งานคำสั่ง Navigator.pop() 
 
bool value = await Navigator.push(context, MaterialPageRoute<bool>(
  builder: (BuildContext context) {
    return Center(
      child: GestureDetector(
        child: Text('OK'),
        onTap: () { Navigator.pop(context, true); }
      ),
    );
  }
));
    จากตัวอย่างจะเห็นว่าเรากำหนดตัวแปร value เป็น boolean เก็บค่า true หรือ false สำหรับรอรับค่าจากคำสั่ง pop()
เมื่อผู้ใช้กดที่ปุ่ม "OK" ก็จะส่งค่า true ซึ่งเป็น result parameter ที่เรากำหนดใว้สำหรับส่งค่ากลับ และค่าตัวแปร value 
ก็จะได้ค่าสุดท้าย จากผู้ใช้เป็น true เพื่อนำไปใช้งานต่อ  สังเกตว่าในส่วนของ MaterialPageRoute<bool> ซึ่งสร้าง route
เราต้องกำหนด generic หรือ type parameter ให้สอดคล้องกับค่าที่เราส่งกลับมานั่นก็คือคือ boolean โดยใช้เป็น <bool>
แต่ถ้าหากเราไม่กำหนดค <type parameter> ก็สามารถทำได้ และค่า type ของ route ที่ return กลับมาก็จะเป็นไปตามชนิด
ของค่าที่ถูกส่งกลับเป็นค่าเริ่มต้น 
 
 

    การใช้งาน Popup Route

    Route ในบางรูปแบบมีการแสดงผลที่ไม่เต็มหน้าจอ เช่น PopupRoute ที่มีการแสดงผลบางส่วน และสามารถมองเห็นหน้า
ก่อนหน้าที่อยู่ด้านล่าง แต่ไม่สามารถจัดการหรือใช้งาน หรือที่เรารู้จักในรูปแบบ "modal" ที่เมื่อเปิด popup มาแล้วเราจะไม่
สามารถใช้งานส่วนที่อยู่ด้านหลังหรือด้านล่างได้ จนกว่าจะปิด popvup ไป 
    มีฟังก์ชั่นการทำงานหลายตัว ที่ใช้ในการสร้างรูปแบบ popup route ยกตัวอย่างเช่น showDialog, showMenu และ 
showModalBottomSheet  โดย popup เหล่านี้จะเป็นในลักษณะ ที่ต้องรอข้อมูลสุดท้าย หรือมีเรื่องของเวลาที่ต้องรอเข้ามา
เกี่ยวข้อง ดังที่อธิบายไปด้านบน
    นอกจากนั้นก็ยังมี  PopupMenuButton และ DropdownButton ที่ใช้สำหรับสร้าง popup route ที่มักใช้งานในภายใน 
subclass ของ PopupRoute อีกที โดยมีการใช้งาน Navigator.push() และ Navigator.pop() เหมือนกับรูปแบบอื่นๆ
    อย่างไรก็ตาม ในส่วนของ popup route จะไมได้ลงในรายละเอียด แต่อาจจะเพิ่มเติมในภายหลัง หากมีเนื้อหาอื่นๆ ที่สัมพันธ์กับการใช้งาน
 
 

    การซ้อนกันของ Navigator

    ใน App ของเราสามารถใช้งาน Navigator ได้มากกว่า 1  ยกตัวอย่างเช่นการใช้งาน tab ที่มีการ ซ้อน navigator ไว้ในอีก
navigator เช่น สำหรับลำดับขั้นตอนการสมัครสมาชิกเป็น step หรือ ใช้ในการกรณีการสั่งซื้อสินค้าที่
มีการลำดับสรูปรายการไปจนถึง ขั้นตอนสุดท้าย การชำระเงิน เป็นต้น เนื้อหาส่วนนี้จะได้อธิบายเพิ่มเติมในหัวข้อต่อๆ ไป
 
     การปรับแต่ง และกำหนด property ต่างๆ เพิ่มเติม สามารถดูได้ที่ Navigator Widget API
     
 
 
 

รู้จัก Widget เพิ่มเติม

    ในบทความต่อๆ ไป เราจะมีหัวข้อสำหรับแนะนำการใช้งาน widget เพิ่มเติมแทรกเข้ามาในบทความนั้น ตามความเหมาะสม
ในที่นี้จะเริ่มต้นที่ SnackBar Widget
    

    การใช้งาน SnackBar Widget

    เนื่องจากในบทความ เราอาจจะมีการใช้งาน SnackBar widget จึงขอแนะนำการใช้งานเบื้องต้นเพิ่มเติม เพื่อให้มีความเข้าใจ
ในบทความได้อย่างต่อเนื่อง
    SnackBar เป็นส่วนสำหรับแสดงข้อความอย่างง่ายในด้านล่างสุดของหน้าจอ โดยเรียกใช้งานผ่านคำสั่ง showSnackBar() ที่เป็น
คำสั่ง ของ ScaffoldMessenger State ในรูปแบบ 
 
ScaffoldMessenger.of(context).showSnackBar()
    โดยค่าเริ่้มต้น จะแสดงข้อความ 4 วินาทีก่อน animate หายไป เราสามารถกำหนดเวลาโดยใช้ duration property ในตอนเรียกใช้งาน
    ตัวอย่างการเรียกใช้งาน
 
  ScaffoldMessenger.of(context)
    ..removeCurrentSnackBar() // ลบ snackBar ถ้ามีอยู่ออก 
    ..showSnackBar(SnackBar(content: Text("Text in snackBar"))); // แล้วแสดงข้อความใหม่
    คำสั่ง removeCurrentSnackBar() จะลบ snackBar ออกทันทีโดยไม่มีการ animate ใดๆ  แต่ถ้าต้องกรให้ค่อยๆ ซ่อนไป สามารถใช้
เป็นคำสั่ง hideCurrentSnackBar() 
 
    การปรับแต่ง และกำหนด property ต่างๆ เพิ่มเติม เช่น สีพื้นหลัง เวลา รูปร่าง สามารถดูได้ที่ SnackBar Widget API
    
 
 
 

ประยุกต์การใช้งาน Navigator

    ในตอนที่แล้ว (ListView) เราสร้างหน้าลิสรายการ Random ไว้ในหน้าแรกในไฟล์ first_screen.dart ที่สามารถเพิ่มรายการได้เรื่อยๆ และสามารถ
กดปุ่มใน actions เพื่อล้างค่าทั้งหมดในครั้งเดียว สิ่งที่เราจะประยุกต์เพิ่มเติมในเนื้อหาในตอนนี้คือ เราจะเพิ่มหน้าจัดการเข้ามาอีก 2 หน้า
ใช้ชื่ออย่างง่าย เป็น secondScreen และ thirdScreen โดยใช้ชื่อไฟล์เป็น second_screen.dart และ third_screen.dart ตามลำดับ
    หน้าที่ 2 เราจะแสดงคล้ายหน้าแรก แต่จะเป็นการเลือกแสดงเฉพาะรายการที่ เรากดเพิ่มเป็น favorite ส่วนหน้าที่ 3 เราจะใช้เป็นหน้าราย
ละเอียดของรายการที่เลือกอย่างง่าย หรือก็คือ หน้า detail สำหรับแสดงข้อมูล รายการที่เรากดเลือก ดังนั้น หน้าที่ 3 สามารถจะแสดงต่อ
จากหน้าแรก หรือหน้าที่สองก็ได้ เมื่อแตะที่รายการที่ต้องการดูรายละเอียด จะได้โครงสร้างไฟล์ ทั้งหมดดังนี้
 
 

 
 
    โค้ดไฟล์เริ่มต้นของทั้ง 3 ไฟล์ screen ตามลำดับ
    ไฟล์ first_screen.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
 
// ส่วนของ Stateful widget
class FirstScreen extends StatefulWidget{
    const FirstScreen({super.key});

    @override
    State<StatefulWidget> createState() {
        return _FirstScreen();
    }
}
class _FirstScreen extends State<FirstScreen>{
    var _randomWord = <WordPair>[];
    final _biggerFont = const TextStyle(color: Colors.black,  fontSize: 20.0);
 
    void _addRandomWord(){
        setState(() {
          _randomWord.addAll(generateWordPairs().take(3).toList());
        });
    }
 
    void _clearRandomWord(){
        setState(() {
            _randomWord.clear();
        });
    }

    void _loadRamdomWord(){
        setState(() {
            _randomWord.addAll(generateWordPairs().take(15).toList());
        });
    }    
 
    Future<void> _loadMoreRandomWord() {
        return Future.delayed(Duration(milliseconds: 10), () {
            setState(() {
                _randomWord.addAll(generateWordPairs().take(20).toList());
            });
        });
    }

    @override
    Widget build(BuildContext context) {
        if (_randomWord.isEmpty){
           _loadRamdomWord();
        }      
        return Scaffold(
            appBar: AppBar(
                title: Text('Welcome to Flutter'),
                backgroundColor: Colors.green,
                actions: <Widget>[
                    IconButton(
                        icon: const Icon(Icons.clear_all),
                        tooltip: 'Clear List',
                        onPressed: _clearRandomWord,
                    ),
                ],
            ),
            body: Container(
                child: ListView.builder(
                    itemCount: _randomWord.length,
                    itemBuilder: (context, index) {
                        if (index == _randomWord.length -1){
                            _loadMoreRandomWord();
                        }                          
                        return _buildRow(_randomWord, index);
                    },
                ),
            ),
            floatingActionButton: FloatingActionButton(
                backgroundColor: Colors.green,
                onPressed: _addRandomWord,
                child: Icon(Icons.add),
            ),
        );
    }
 
    Widget _buildRow(randomWord, index) {
        return Container(
            child: Column(
                children: <Widget>[
                    ListTile(
                      // title: Text('${randomWord[index]}'),
                        title: Text('${randomWord[index].asPascalCase}'),
                        onTap: (){},
                    ),
                    const Divider(),
                ],
            ),
        );
    }
}
    ไฟล์ second_screen.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
 
class SecondScreen extends StatefulWidget {
    const SecondScreen({super.key});

    @override
    State<StatefulWidget> createState() {
        return _SecondScreen();
    }
}
 
class _SecondScreen extends State<SecondScreen> {
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Second Screen'),
                backgroundColor: Colors.green,
            ),
            body: Center(
                child: Text('Second Screen'),
            ),
        );
    }
}
    ไฟล์ third_screen.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
 
class ThirdScreen extends StatefulWidget {
    const ThirdScreen({super.key});

    @override
    State<StatefulWidget> createState() {
        return _ThirdScreen();
    }
}
 
class _ThirdScreen extends State<ThirdScreen> {
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Third Screen'),
                backgroundColor: Colors.green,
            ),
            body: Center(
                child: Text('Third Screen'),
            ),
        );
    }
}
 

    ประยุกต์ใช้งาน เปิด-ปิด Route

    สิ่งแรกที่เราจะทำคือ การกดกลับไป-มา ระหว่างหน้าแรก กับหน้าที่สอง ซึ่งไม่ได้สัมพันธ์กันโดยตรง เหมือนลักษณะการเปลี่ยนหน้า
เกี่ยวกับเรา ไปแสดงหน้าติดต่อเราในเว็บไซต์ โดยเราจะเพิ่ม ปุ่ม iconButton เข้าไปใน action ตรง AppBar ในหน้าแรก เพื่อเปิดหน้า
ลิสรายการที่ชื่นชอบ เริ่มต้นให้เรา import SecondScreen class มาใช้งานในไฟล์ first_screen.dart
 
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
import './second_screen.dart';
    จากนั้นในส่วนของ AppBar ก็กำหนดการเรียกใช้งานเป็นดังนี้
 
 

 
 
appBar: AppBar(
    title: Text('Welcome to Flutter'),
    backgroundColor: Colors.green,
    actions: <Widget>[
        IconButton(
            icon: const Icon(Icons.clear_all),
            tooltip: 'Clear List',
            onPressed: _clearRandomWord,
        ),
        IconButton(
            icon: const Icon(Icons.favorite),
            tooltip: 'Favorite List',
            onPressed: () {
                Navigator.push(
                  context, 
                  MaterialPageRoute(builder: (context) => SecondScreen()),
                );                
            }
        ),
    ],
),
 
    ในโค้ดจะเห็นว่าเราสร้าง iconButton เพิ่มเข้าไปใน AppBar และเมื่อกดที่ปุ่ม ก็จะทำการเปิดหน้าใหม่ โดยใช้คำสั่ง Navigator.push()
ตามตัวอย่างข้างต้น ผลลัพธ์ที่ได้ก็จะเป็นดังรูปด้านล่าง 
 
 

 
 
    เมื่อเรากดปุ่มรูปหัวใจ ก็เปิดหน้าใหม่ขึ้นมา ในหน้าต่างใหม่ เราสามารถสร้างปุ่ม และกำหนดเรียกใช้ Navigator.pop(context); เพื่อปิด
หน้าที่กำลังเปิดอยู่ แล้วกลับไปยังหน้าก่อนหน้า แต่ในตัวอย่างเราไม่ได้เพิ่มปุ่มดังกล่าวไป แต่สามารถใช้ปุ่มอัตโนมัติใน AppBar เพื่อย้อน
กลับไปหน้าก่อนหน้าได้ หรือจะกดปุ่มด้านล่างของ System UI เพื่อกลับหน้าก่อนหน้าก็ได้เหมือนกัน
 
    เราสามารถประยุกต์ลักษณะเดียวกับ กับการแตะที่รายการใน ListView โดยให้เปิดไปหน้าที่ 3 หรือหน้ารายละเอียด
    โดย import ThirdScreen class มาใช้งานในไฟล์ first_screen.dart แล้วใช้รูปแบบการเรียกใช้งานเหมือนกับปุ่ม favorite icon
 
 
import './third_screen.dart';
 

 
 
Widget _buildRow(randomWord, index) {
    return Container(
        child: Column(
            children: <Widget>[
                ListTile(
                    title: Text('${randomWord[index].asPascalCase}'),
                    onTap: (){
                      Navigator.push(
                        context, 
                        MaterialPageRoute(builder: (context) => ThirdScreen()),
                      );                              
                    },
                ),
                const Divider(),
            ],
        ),
    );
}
 
    ผลลัพธ์ที่ได้
 
 

 
 
    จะเห็นว่า เมื่อเราแตะรายการจากลิส ก็จะเปิดหน้ารายละเอียดซ้อนขึ้นมาทับอยู่ด้านบนของหน้าลิสรายการ และเมื่อเรากด back เพื่อย้อน
กลับ ก็จะทำการเอาหน้าบนสุดออกหรือก็คือหน้ารายละเอียดออกไป เราก็จะเห็นหน้าลิสรายการอีกครั้ง
    
 

    ประยุกต์ใช้งาน กำหนดชื่อให้กับ Route

    ตอนนี้เราพอได้แนวทางในการเปิด ปิดหน้าใหม่เบื้องต้นแล้ว ซึ่งถ้าใช้สำหรับเปิดหน้า เฉพาะไม่กี่หน้า หรือใช้งานในบางส่วน เราก็สามารถ
ใช้รูปแบบนี้ได้  แต่ถ้าหากมี route จำนวนมากๆ วิธีที่เหมาะสมควร คือเราควรกำหนดชื่อ เพื่อเรียกใช้งานจะได้สะดวกขึ้น ซึ่งเราจะใช้งาน
MaterialApp widget ในการกำหนดชื่อให้กับ routes แต่ละตัว จะได้ไฟล์ main.dart เป็นดังนี้
 
    ไฟล์ main.dart
import 'package:flutter/material.dart';
import './app_screen/first_screen.dart';
import './app_screen/second_screen.dart';
import './app_screen/third_screen.dart';

void main(){
    runApp(MyApp());
}

// ส่วนของ Stateless widget
class MyApp extends StatelessWidget{
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'First Flutter App',
//            home: FirstScreen(),
            initialRoute: '/', // สามารถใช้ home แทนได้
            routes: {
                '/': (context) => FirstScreen(),
                '/second': (context) => SecondScreen(),
                '/third': (context) => ThirdScreen(),
            },
        );
    }
}
    ตอนนี้เรากำหนดชื่อให้กับ route ทั้งหมดแล้ว โดยกำหนดไว้ที่ MaterialApp ซึ่งเป้นลำดับโครงสร้าง widget ที่อยู่ด้านบนสุด
หรือก็คือ Root widget เราสามารถเรียกใช้งานชื่อ ของ route ในไฟล์ first_screen.dart แทนการเรียกใช้ผ่าน MaterialPageRoute
ดังนั้นเราสามารถปรับโค้ดก่อนหน้าเป็นดังนี้ ตามลำดับ
 
 
Navigator.pushNamed(context, '/second'); 
Navigator.pushNamed(context, '/third'); 
 
 

 
 
    ผลลัพธ์ที่ได้ ก็เหมือนเดิม แต่เราใช้คำสั่ง pushNamed() แทน push() ทำให้อ้างอิงหน้า route ได้ง่ายและสะดวกขึ้น เหมาะสำหรับ
ใช้งาน route จำนวนมากๆ แต่ถ้ามีการส่งค่าไปด้วย ก็จะมีวิธีจัดการที่ยุ่งยากกว่ากรณีใช้งานแบบแรกที่ใช้เป็น MaterialPageRoute ใน
การสร้าง route
 
 

    ประยุกต์ใช้งาน ส่งข้อมูลไปยัง Route

    เราจะประขุกต์การส่งข้อมูลจากหน้าแรก ไปยังหน้ารายละเอียด หรือหน้าที่ 3 โดยเมื่อแตะเลือกรายการที่ต้องการ เดิมก็จะแค่เปิดหน้า
รายละเอียดขึ้นมา แต่เราจะทำการส่งข้อมูลไปใช้ในหน้ารายละเอียดด้วย ซึ่งในการใช้งาน ก็จะมีวิธีการในหลายวิธี เช่นใช้งานร่วมกับ
MaterialPageRoute เพื่อสร้าง route โดยสามารถใช้การส่งแบบ arguments เข้าไป วิธีนี้จะง่ายที่สุด  อีกวิธีคือส่งไป ผ่านการกำหนด
เป็น parameter constructor ของ widget ส่วนนี้ จะเพิ่มความยากเข้ามาหน่อย โดยเราต้องกำหนดตัวแปรสำหรับรับค่า และกำหนด
constructor ให้รองรับการส่งค่ามาด้วย  ส่วนอีกวิธี ที่ค่อนข้างซับซ้อน ก็คือกรส่งโดยใช้งานร่วมกับการกำหนดชื่อให้กับ route เป็น 
arguments ค่าหนึ่งไปด้วย 
    เราจะเริ่มต้นที่วิธีที่ง่ายที่สุดก่อน นั่นคือใช้งาน MaterialPageRoute เพื่อสร้าง route แล้วส่งเป็น arguments เข้าไป ในที่นี้จะใช้วการ
ส่งข้อมูล เป็นข้อความอย่างง่าย หรือก็คือ ส่งข้อความ Random ที่เลือก ไปแสดงอีกหน้า ดังนี้
    ไฟล์ first_screen.dart ส่วนของการ แตะที่ข้อความ Random
 
 
กรณีใช้งาน Navigator.pushNamed ใช้กับ route ที่กำหนดชื่อ
 
onTap: (){
  // กำหนดรูปแบบข้อมูลเป็นแบบ Map
  Map<String, dynamic> args = {
    "msg": randomWord[index].asPascalCase.toString()
  };
  Navigator.pushNamed(
      context,
      '/third',
      arguments: args  // ส่งค่าไปใน  arguments          
  ); 
},
 
กรณีใช้งาน Navigator.push ใช้กับ route ที่ไม่กำหนดชื่อ
 
onTap: (){
  // กำหนดรูปแบบข้อมูลเป็นแบบ Map
  Map<String, dynamic> args = {
    "msg": randomWord[index].asPascalCase.toString()
  };
  Navigator.push(
    context, 
    MaterialPageRoute(builder: (context) => ThirdScreen(),
      settings: RouteSettings(
        arguments: args // ส่งค่าไปใน  arguments   
      ),
    ),
  ); 
},
 
    จะเห็นว่าเราเพิ่ม settings ให้กับ MaterialPageRoute โดยใช้งาน RouteSettings กำหนด ค่า arguments เป็นข้อความที่เลือก
 
    ในไฟล์ third_screen.dart เราก็จะทำการรับค่า และแสดงแทนข้อความเดิม โค้ดเฉพาะส่วนของ _ThirdScreen state
 
 
class _ThirdScreen extends State<ThirdScreen> {
    // รูปแบบการแสดงข้อความ
    final _biggerFont = const TextStyle(color: Colors.black,  fontSize: 20.0);
 
    @override
    Widget build(BuildContext context) {
        // รับค่า arguments ที่ส่งมาใช้งาน
        final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;  
        // แสดงค่าหรือใช้งานผ่านรูปแบบ args['msg']

        return Scaffold(
            appBar: AppBar(
                title: Text('Third Screen'),
                backgroundColor: Colors.green,
            ),
            body: Center(
                child: Text(args['msg'],style: _biggerFont),
            ),
        );
    }
}
 
    ผลลัพธ์ที่ได้
 
 

 
 
    จะเห็นว่า เมื่อเราแตะเลือกรายการใดๆ ก็จะทำการเปิดหน้า route รายละเอียด พร้อมกับส่งค่าคำที่เราเลือก ไปใช้งานในหน้า
ดังกล่าวด้วย ในตัวอย่าง เราจำลองส่งข้อมูลอย่างง่าย เป็นเพียงข้อความไป การใช้งานจริง เราสามารถส่งข้อมูลชนิดต่างๆ ไปได้
เช่น ส่งเป็น Object ข้อมูลที่ อาจจะมีข้อมูลย่อยๆ อื่นๆ เพิ่มเติมได้ เป็นต้น
 
    ต่อไปเป็นวิธีที่ 2 เราจะส่งไปกับ parameter constructor ดงนั้น ส่วนนี้ เราต้องไปกำหนด parameter constuctor ให้กับ
widget ก่อน เพื่อรอรับการกำหนดค่า เพื่อใช้งานต่อ
    สำหรับ constructor เราจะกำหนดไว้ใน StatefulWidget ส่วนการเรียกใช้งาน เราจะใช้งานใน State Wdget ผ่านรูปแบบ
widget.[ชือ property ข้อมูลที่ส่งมา] เดียวทำความเข้าใจในโค้ดตัวอย่าง ให้เริ่มต้นที่ไฟล์ third_screen.dart  กำหนด property
สำหรับรับค่า พร้อมกำหนด parameter contructor ดังนี้
 
 
class ThirdScreen extends StatefulWidget {
    String selectedWord = ''; // กำหนด property สำหรับรับค่า

    ThirdScreen({Key? key, required this.selectedWord}) : super(key: key);
    
    @override
    State<StatefulWidget> createState() {
        return _ThirdScreen();
    }
}
 
    ต่อไป เรียกใช้งาน propery ใน _ThirdScreen State โดยอ้างอิง ThirdScreen Wdget ผ่านตัวแปร "widget" โดยสามารถเรียก
ใช้งาน selectedWord ได้ในรูปแบบ widget.selectedWord จะได้เป็น
 
 
class _ThirdScreen extends State<ThirdScreen> {
    // รูปแบบการแสดงข้อความ
    final _biggerFont = const TextStyle(color: Colors.black,  fontSize: 20.0);
 
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Third Screen'),
                backgroundColor: Colors.green,
            ),
            body: Center(
                child: Text(widget.selectedWord,style: _biggerFont),
            ),
        );
    }
}
 
    เป็นอันเรียบร้อยสำหรับหน้ารับค่า
 
     ต่อไปในหน้าส่งค่า เราก็ทำการเพิ่ม options parameter เข้าไป ตอนเรียกใช้งาน ThirdScreen widget ดังนี้
     ไฟล์ first_screen.dart ในส่วนของ onTap ลิสรายการ
 
 
onTap: (){
  Navigator.push(
    context, 
    MaterialPageRoute(
      builder: (context) => ThirdScreen(selectedWord: randomWord[index].asPascalCase.toString()),
    ),
  );  
},
 
    เรากำหนด parameter เข้าไปตอนเรียกใช้งาน ThirdScreen Wdget 
    เมื่อทดสอบรัน ผลลัพธ์  ที่ได้ ก็จะเหมือนกับในวิธีแรก เมื่อเลือกข้อความ ก็จะเปิดหน้ารายละเอียด พร้อมแสดงข้อความที่เลือก
 
    ต่อไปเป็นวิธีที่ 3 ซึ่งวิธีนี้จะคล้ายกับวิธีแรก ที่ทำการส่งข้อมูลเป็นค่า arguments เข้าไปและใช้วิธีการรับค่าในหน้ารายละเอียด
เหมือนกัน แต่เนื่องจากวิธีแรก เป็นการใช้งาน MaterialPageRoute สร้าง route และกำหนด arguments แต่วิธีนี้ เราใช้งานโดย
ใช้เป็นชื่อ route แทน ดังนั้นในไฟล์ third_screen.dart ส่วนของ ThirdScreen widget ให้เรากำหนด static const สำหรับ
กำหนดชื่อ route สำหรับเรียกใช้งานอีกที จะได้เป็น
 
 
class ThirdScreen extends StatefulWidget {
    static const routeName = '/third';
    
    @override
    State<StatefulWidget> createState() {
        return _ThirdScreen();
    }
}
 
    ส่วนของ _ThirdScreen State ก็ใช้วิธีเดียวกันกับในวิธีแรก ในการรับค่า และใช้งาน
 
 
class _ThirdScreen extends State<ThirdScreen> {
    // รูปแบบการแสดงข้อความ
    final _biggerFont = const TextStyle(color: Colors.black,  fontSize: 20.0);
 
    @override
    Widget build(BuildContext context) {
        // รับค่า arguments ที่ส่งมาใช้งาน
       final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;  
        // แสดงค่าหรือใช้งานผ่านรูปแบบ args['msg']

        return Scaffold(
            appBar: AppBar(
                title: Text('Third Screen'),
                backgroundColor: Colors.green,
            ),
            body: Center(
                child: Text(args['msg'],style: _biggerFont),
            ),
        );
    }
}
 
    ต่อไปส่วนของไฟล์ first_screen.dart ในส่วนของ onTap เราใช้เป็น Navigator.pushNamed() แทน และส่งข้อมูลไปใน
การกำหนดค่า arguments ดังนี้
 
 
onTap: (){
  // กำหนดรูปแบบข้อมูลเป็นแบบ Map
  Map<String, dynamic> args = {
    "msg": randomWord[index].asPascalCase.toString()
  };
  Navigator.pushNamed(
      context,
      ThirdScreen.routeName,
      arguments: args  // ส่งค่าไปใน  arguments          
  ); 
},
 
    จะเห็นว่า ในส่วนของชื่อ route เราใช้ชื่อจาก static const ที่กำหนด โดยเรียกใช้งานในรูปแบบ ThirdScreen.routeName
และค่า arguments เราก็กำหนดข้อความที่จะส่งไป ยังไม่จบเท่านี้ 
    สุดท้ายให้เราไปแก้ไขในส่วนของไฟล์ main.dart ที่มีการกำหนดชื่อ route เราต้องแก้ไขในส่วนของหน้ารายละเอียด
โดยเรียกใช้เป็นค่าจากตัวแปร static แทนดังนี้
 
 
routes: {
    '/': (context) => FirstScreen(),
    '/second': (context) => SecondScreen(),
    ThirdScreen.routeName: (context) => ThirdScreen(),
},
 
    จากเดิมเรากำหนดโดยตรงเป็น '/third' ก็เปลี่ยนมาใช้การอ้างอิงจากตัวแปร static ที่เรากำหนดแทน
    ผลลัพธ์ของวิธี่ที่ 3 ก็เป็นเหมือนกับวิธีก่อนๆ ที่ผ่านมา แต่ข้อดีคือ รองรับการใช้งานการกำหนดชื่อ route
 
    เราจะคงรูปแบบการส่งค่าวิธีนี้ในโปรเจ็คตัวอย่างไปก่อน และไปต่อที่การประยุกต์ต่อไป
 
 

    ประยุกต์ใช้งาน รับค่าจาก Route

    การใช้งานรับค่าจาก route ในหัวข้อนี้ จะไม่ใช่รุปแบบเดียวกับหัวข้อที่ผ่านมา ที่รับค่ามาแล้วแสดง แต่ในที่นี้ จะเป็นการรับค่าจากหน้า
route ที่เราทำการปิด และส่งค่า parameter กลับมา รูปแบบเช่นเดียวกับ popup ยืนยัน ที่รอให้ผู้ใช้เลือกข้อมูล แล้วส่งกลับมาใช้งาน
ในหน้าหลัก หัวข้อนี้เราจะใช้งาน snackBar widget ประกอบด้วย
    แนวทางของเราคือ เมื่อเราเลือกรายการใดๆ แล้วก็แสดงข้อมูลรายการนั้นในหน้ารายละเอียด โดยมีปุ่ม สองปุ่ม คือ Yes กับ No ว่า
ชอบข้อความนี้หรือไม่ ถ้าผู้ใช้เลือกอย่างใดๆ อย่างหนึ่ง ก็จะปิดหน้านั้นไป พร้อมกับส่งค่าที่เลือก กลับมาแสดงใน snackBar ในหน้าหลัก
    สิ่งที่เราจะทำคือ จัดหน้ารายละเอียดในไฟล์ third_screen.dart เพิ่มข้อความ และปุ่ม พร้อมเรียรกใช้งาน Navigator.pop() ส่งค่า
ตัวเลือกที่ผู้ใช้เลือกกลับมา ดังนี้
 
class _ThirdScreen extends State<ThirdScreen> {
    // รูปแบบการแสดงข้อความ
    final _biggerFont = const TextStyle(color: Colors.black,  fontSize: 20.0);
 
    @override
    Widget build(BuildContext context) {
        // รับค่า arguments ที่ส่งมาใช้งาน
       final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;  
        // แสดงค่าหรือใช้งานผ่านรูปแบบ args['msg']

        return Scaffold(
            appBar: AppBar(
                title: Text('Third Screen'),
                backgroundColor: Colors.green,
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                        Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Text(args['msg'],style: _biggerFont),
                        ),
                        Text("Do you like this word?"),
                        Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                                Padding(
                                    padding: const EdgeInsets.all(8.0),
                                    child: ElevatedButton(
                                        onPressed: () => Navigator.pop(context, 'Yep!'),
                                        child: Text('Yep!'),
                                    ),
                                ),
                                Padding(
                                    padding: const EdgeInsets.all(8.0),
                                    child: ElevatedButton(
                                        onPressed: () => Navigator.pop(context, 'Nope.'),
                                        child: Text('Nope.'),
                                    ),
                                )
                            ],
                        ),
                    ],
                ),
            ),
        );
    }
}
    สังเกตในส่วนของคำสั่ง ของทั้งสองปุ่ม เราใช้รูปแบบการเรียกใช้งานแบบ Fat Arrow หรือ Short Syntax ใช้งานคำสั่ง 
Navigator.pop() โดยมีการกำหนด parameter ที่เรียกว่า "result" parameter สำหรับส่งกลับข้อความ "Yep!" หรือ "Nope."
แล้วแต่ผู้ใช้จะเลือกรายการไหน
    เราได้อธิบายหลักการไปแล้วในตอนต้นเรื่องการคืนค่าเมื่อปิดหรือ route ที่มีการรอรับค่าจากผู้ใช้ ดังนั้น ก่อนที่เราจะเปิดหน้า
รายละเอียด เราต้องใช้รูปแบบเป็น Future หรือมีเรื่องของเวลาที่ต้องรอเข้ามาเกี่ยวข้อง นั่นคือ เมื่อเปิดหน้ารายละเอียดแล้ว เรามี
การรอค่าสุดท้ายจากหน้านั้น โดยใช้ await keyword ซึ่งจะต้องใช้งานกับ async ฟังก์ชั่น ดังนั้น ให้เราสร้างฟังก์ชั่น asynchronous
มาใช้งาน เราจะได้ไฟล์ first_screen.dart ทั้งหมดดังนี้
 
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
import './second_screen.dart';
import './third_screen.dart';
 
// ส่วนของ Stateful widget
class FirstScreen extends StatefulWidget{
    const FirstScreen({super.key});

    @override
    State<StatefulWidget> createState() {
        return _FirstScreen();
    }
}
class _FirstScreen extends State<FirstScreen>{
    var _randomWord = <WordPair>[];
    final _biggerFont = const TextStyle(color: Colors.black,  fontSize: 20.0);
  
    void _addRandomWord(){
        setState(() {
            _randomWord.addAll(generateWordPairs().take(3));
        });
    }
  
    void _clearRandomWord(){
        setState(() {
            _randomWord.clear();
        });
    }
  
    void _loadRamdomWord(){
        setState(() {
            _randomWord.addAll(generateWordPairs().take(15));
        });
    }
  
    Future<void> _loadMoreRandomWord() {
        return Future.delayed(Duration(milliseconds: 10), () {
            setState(() {
                _randomWord.addAll(generateWordPairs().take(20));
            });
        });
    }
  
    @override
    Widget build(BuildContext context) {
        if (_randomWord.isEmpty){
            _loadRamdomWord();
        }
        return Scaffold(
            appBar: AppBar(
                title: Text('Welcome to Flutter'),
                backgroundColor: Colors.green,
                actions: <Widget>[
                    IconButton(
                        icon: const Icon(Icons.clear_all),
                        tooltip: 'Clear List',
                        onPressed: _clearRandomWord,
                    ),
                    IconButton(
                        icon: const Icon(Icons.favorite),
                        tooltip: 'Favorite List',
                        onPressed: () {
                          Navigator.pushNamed(context, '/second');
                            // Navigator.push(
                            //   context, 
                            //   MaterialPageRoute(builder: (context) => SecondScreen()),
                            // );                
                        }
                    ),
                ],
            ),
            body: Container(
                child: ListView.builder(
                        itemCount: _randomWord.length,
                        itemBuilder: (context, index) {
                            if (index == _randomWord.length -1){
                                _loadMoreRandomWord();
                            }
                            // เพิ่ม context
                            return _buildRow(context,_randomWord, index);
                        },
                ),
            ),
            floatingActionButton: FloatingActionButton(
                backgroundColor: Colors.green,
                onPressed: _addRandomWord,
                child: Icon(Icons.add),
            ),
        );
    }
  
    // เพิ่ม context เป็น paramter
    Widget _buildRow(context, randomWord, index) {
        return Container(
            child: Column(
                children: <Widget>[
                    ListTile(
                        title: Text('${randomWord[index].asPascalCase}'),
                        onTap: (){                       
                          _showYourChoice(context, randomWord[index].asPascalCase);
                        },
                    ),
                    const Divider(),
                ],
            ),
        );
    }
 
    _showYourChoice(context, arg) async{ 
    // กำหนดรูปแบบข้อมูลเป็นแบบ Map
        Map<String, dynamic> args = {
          "msg": arg.toString()
        };   
        // เมื่อเปิดหน้าใหม่ ให้รอผลลัพธ์สุดท้าย ถ้ามีการปิดหน้าที่เปิด      
        final result = await Navigator.pushNamed(
            context,
            ThirdScreen.routeName,
            arguments: args
        );
  
        // ส่วนสำหรับแสดงข้อความด้านล่างขอบหน้าจอ
        ScaffoldMessenger.of(context)
            ..removeCurrentSnackBar()
            ..showSnackBar(SnackBar(content: Text("$result")));
    }    
  
}
   เหตุผลที่ยกโค้ดมาทั้งหมด เพราะจะให้ดูว่า เรามีการแก้ไขฟังก์ชั่น _buildRow(context, randomWord, index) โดยเพิ่ม context
paramter เข้ามา ทั้งนี้เพราะ เราจะมีการใช้งาน SnackBar ซึ่งจะใช้ propery จาก ScaffoldMessenger State เพื่อเรียกใช้คำสั้ง 
removeCurrentSnackBar() และ showSnackBar() โดย Scaffold.of() เป็น static method ที่ช่วยให้เราสามารถเรียกใช้งาน method
ต่างๆ จาก InheritedWidget เข้าใจอย่างง่ายคือ เราใช้งาน Scaffold ในโครงสร้างลำดับขึ้นด้านบนสุด และต้องการใช้ property
ของ widget นี้ ดังนั้น เราต้องส่งต่อ context ของ widget ลงมาในฟังก์ชั่นนี้เพื่อใช้งาน นั่นคือ context ต้องส่งต่อมาจาก _buildRow
ฟังก์ชั่น และส่งต่อมาใน _showYourChoice ฟังก์ชั่น อีกที  จะได้พูดถึง InheritedWidget เพิ่มเติมในตอนหน้ารอติดตาม
    จากตัวอย่าง เรากำหนดตัวแปร result ให้เราค่าจากหน้ารายละเอียด เมื่อได้ค่าสุดท้ายแล้ว ซึ่งจะเป็นข้อความ "Yep!" หรือข้อความ
"Nope." ถ้าเลือกปุ่มอย่างใดอย่างหนึ่ง เราก็นำค่ามาแสดงใน snackBar ดูตัวอย่างผลลัพธ์เมื่อรันการทำงานดังรูปด้านล่าง
 
 

 
 
    ตอนนี้เราแทบจะครอบคลุมเนื้อหาเบี้องต้น เกี่ยวกับการใช้งาน Navigator และ Route แล้ว เหลือแต่เพียงการประยุกต์สุดท้าย คือ
สร้างรายการ favorite สำหรับใช้ในหน้าที่ 2 ขอรวบรัดเป็นโค้ดทั้งหมด ทั้ง 4 ไฟล์ และตัวอย่างผลลัพธ์ทิ้งท้าย
 
    ไฟล์ main.dart
import 'package:flutter/material.dart';
import './app_screen/first_screen.dart';
import './app_screen/second_screen.dart';
import './app_screen/third_screen.dart';
 
void main(){
    runApp(MyApp());
}
 
// ส่วนของ Stateless widget
class MyApp extends StatelessWidget{
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'First Flutter App',
            // home: FirstScreen(),
            initialRoute: '/', // สามารถใช้ home แทนได้
            routes: {
                '/': (context) => FirstScreen(),
                '/second': (context) => SecondScreen(),
                ThirdScreen.routeName: (context) => ThirdScreen(),
            },            
        );
    }
}
    ไฟล์ first_screen.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
import './second_screen.dart';
import './third_screen.dart';
 
// ส่วนของ Stateful widget
class FirstScreen extends StatefulWidget{
    const FirstScreen({super.key});

    @override
    State<StatefulWidget> createState() {
        return _FirstScreen();
    }
}
class _FirstScreen extends State<FirstScreen>{
    var _randomWord = <WordPair>[];
    var _favorite = <WordPair>[]; // ส่วนที่เพิ่มมาใหม่ **
    final _biggerFont = const TextStyle(color: Colors.black,  fontSize: 20.0);
  
    void _addRandomWord(){
        setState(() {
            _randomWord.addAll(generateWordPairs().take(3));
        });
    }
  
    void _clearRandomWord(){
        setState(() {
            _randomWord.clear();
        });
    }
  
    void _loadRamdomWord(){
        setState(() {
            _randomWord.addAll(generateWordPairs().take(15));
        });
    }
  
    Future<void> _loadMoreRandomWord() {
        return Future.delayed(Duration(milliseconds: 10), () {
            setState(() {
                _randomWord.addAll(generateWordPairs().take(20));
            });
        });
    }
  
    @override
    Widget build(BuildContext context) {
        if (_randomWord.isEmpty){
            _loadRamdomWord();
        }
        return Scaffold(
            appBar: AppBar(
                title: Text('Welcome to Flutter'),
                backgroundColor: Colors.green,
                actions: <Widget>[
                    IconButton(
                        icon: const Icon(Icons.clear_all),
                        tooltip: 'Clear List',
                        onPressed: _clearRandomWord,
                    ),
                    IconButton(
                        icon: const Icon(Icons.favorite),
                        tooltip: 'Favorite List',
                        onPressed: () {
                          // ปรับส่วนของการส่งไปหน้าที่สอง
                          Navigator.pushNamed(
                              context,
                              SecondScreen.routeName,
                              arguments: _favorite
                          );                                  
                        }
                    ),
                ],
            ),
            body: Container(
                child: ListView.builder(
                        itemCount: _randomWord.length,
                        itemBuilder: (context, index) {
                            if (index == _randomWord.length -1){
                                _loadMoreRandomWord();
                            }
                            // เพิ่ม context
                            return _buildRow(context,_randomWord, index);
                        },
                ),
            ),
            floatingActionButton: FloatingActionButton(
                backgroundColor: Colors.green,
                onPressed: _addRandomWord,
                child: Icon(Icons.add),
            ),
        );
    }
  
    // เพิ่ม context เป็น paramter
    Widget _buildRow(context, randomWord, index) {
        bool _alreadyFavorite = _favorite.contains(randomWord[index]);
        return Container(
            child: Column(
                children: <Widget>[
                    ListTile(
                        title: Text('${randomWord[index].asPascalCase}'),
                        trailing: Icon(
                            _alreadyFavorite ? Icons.favorite : Icons.favorite_border,
                            color: _alreadyFavorite ? Colors.red : null,
                        ),                        
                        onTap: (){                       
                          _showYourChoice(context, randomWord[index]);
                        },
                    ),
                    const Divider(),
                ],
            ),
        );
    }
 
    _showYourChoice(context, arg) async{ 
    // กำหนดรูปแบบข้อมูลเป็นแบบ Map
        Map<String, dynamic> args = {
          "msg": arg.asPascalCase.toString()
        };   
        // เมื่อเปิดหน้าใหม่ ให้รอผลลัพธ์สุดท้าย ถ้ามีการปิดหน้าที่เปิด      
        final result = await Navigator.pushNamed(
            context,
            ThirdScreen.routeName,
            arguments: args
        );
  
        // จัดการเงื่อนไขรับค่าที่ส่งกลับมา
        setState(() {
            if (result == 'Nope.') {
                _favorite.remove(arg);
            } else {
                _favorite.add(arg);
            }
        });
 
        // ส่วนสำหรับแสดงข้อความด้านล่างขอบหน้าจอ
        ScaffoldMessenger.of(context)
            ..removeCurrentSnackBar()
            ..showSnackBar(SnackBar(content: Text("$result")));
    }    
  
}
    ไฟล์ second_screen.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
import './third_screen.dart';
 
class SecondScreen extends StatefulWidget {
    static const routeName = '/second'; // กำหนดชื่อให้ route

    @override
    State<StatefulWidget> createState() {
        return _SecondScreen();
    }
}
 
class _SecondScreen extends State<SecondScreen> {
    var _favorite = <WordPair>[];

    @override
    Widget build(BuildContext context) {
       _favorite = ModalRoute.of(context)!.settings.arguments as List<WordPair>;

        return Scaffold(
            appBar: AppBar(
                title: Text('Second Screen'),
                backgroundColor: Colors.green,
            ),
            body: Container(
                child: ListView.builder(
                    itemCount: _favorite.length,
                    itemBuilder: (context, index) {
                            return _buildRow(context, _favorite, index);
                    },
                ),
            ),
        );
    }

    Widget _buildRow(context, favoriteWord, index) {
        bool _alreadyFavorite = _favorite.contains(favoriteWord[index]);
        return Container(
            child: Column(
                children: <Widget>[
                    ListTile(
                        title: Text('${favoriteWord[index].asPascalCase}'),
                        trailing: Icon(
                            _alreadyFavorite ? Icons.favorite : Icons.favorite_border,
                            color: _alreadyFavorite ? Colors.red : null,
                        ),
                        onTap: () {
                            _showYourChoice(context, favoriteWord[index]);
                        },
                    ),
                    const Divider(),
                ],
            ),
        );
    }    

    _showYourChoice(context, arg) async{ 
    // กำหนดรูปแบบข้อมูลเป็นแบบ Map
        Map<String, dynamic> args = {
          "msg": arg.asPascalCase.toString()
        };   

        // เมื่อเปิดหน้าใหม่ ให้รอผลลัพธ์สุดท้าย ถ้ามีการปิดหน้าที่เปิด      
        final result = await Navigator.pushNamed(
            context,
            ThirdScreen.routeName,
            arguments: args
        );
 
        // จัดการเงื่อนไขรับค่าที่ส่งกลับมา
        setState(() {
            if (result == 'Nope.') {
                _favorite.remove(arg);
            } else {
                _favorite.add(arg);
            }
        });

        // ส่วนสำหรับแสดงข้อความด้านล่างขอบหน้าจอ
        ScaffoldMessenger.of(context)
            ..removeCurrentSnackBar()
            ..showSnackBar(SnackBar(content: Text("$result")));
    }      
}
    ไฟล์ third_screen.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
 
class ThirdScreen extends StatefulWidget {
    static const routeName = '/third';
    
    @override
    State<StatefulWidget> createState() {
        return _ThirdScreen();
    }
}
 
class _ThirdScreen extends State<ThirdScreen> {
    // รูปแบบการแสดงข้อความ
    final _biggerFont = const TextStyle(color: Colors.black,  fontSize: 20.0);
 
    @override
    Widget build(BuildContext context) {
        // รับค่า arguments ที่ส่งมาใช้งาน
       final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;  
        // แสดงค่าหรือใช้งานผ่านรูปแบบ args['msg']

        return Scaffold(
            appBar: AppBar(
                title: Text('Third Screen'),
                backgroundColor: Colors.green,
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                        Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Text(args['msg'],style: _biggerFont),
                        ),
                        Text("Do you like this word?"),
                        Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                                Padding(
                                    padding: const EdgeInsets.all(8.0),
                                    child: ElevatedButton(
                                        onPressed: () => Navigator.pop(context, 'Yep!'),
                                        child: Text('Yep!'),
                                    ),
                                ),
                                Padding(
                                    padding: const EdgeInsets.all(8.0),
                                    child: ElevatedButton(
                                        onPressed: () => Navigator.pop(context, 'Nope.'),
                                        child: Text('Nope.'),
                                    ),
                                )
                            ],
                        ),
                    ],
                ),
            ),
        );
    }
}
    ผลลัพธ์การทำงาน เริ่มต้น คลิกเลือกรายการที่ต้องการ แล้วเลือกเป็น favorite จากตัวเลือกหน้ารายละเอียด ถ้าตอบ "Yep!" รายการ
จะถูกเพิ่มเข้าไปใน favorite เมื่อเปิดไปหน้าที่สอง จะแสดงรายการทีถูกเลือกทั้งหมด
 
 

 
 
    หัวข้อในตอนหน้า เราจะพูดถึงการใช้งาน InheritedWidget และการจัดการ Theme ของโปรเจ็คตัวอย่างต่อ รอติดตาม


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



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



ทบทวนบทความที่แล้ว









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









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





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







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