Skip to main content

เนื้อหาภาษาไทย / Thai content

System guide Content reviewed

การจัดการ Navigation ด้วย Named Routes และ Navigator 2.0

บทความนี้อธิบายถึงวิธีการจัดการ Navigation ใน Flutter โดยครอบคลุมทั้งการใช้ `Navigator.push` และ `Navigator.pop` แบบพื้นฐาน, การใช้ Named Routes เพื่อให้โค้ดสะอ...

การจัดการ Navigation ด้วย Named Routes และ Navigator 2.0 ใน Flutter

ในบทความก่อนหน้า เราได้เรียนรู้เกี่ยวกับการใช้ ThemeData และ Custom Themes เพื่อจัดการรูปแบบของแอปพลิเคชัน Flutter ของเรา ในบทนี้ เราจะมาเจาะลึกเรื่องการนำทาง (Navigation) ซึ่งเป็นส่วนสำคัญในการสร้างแอปพลิเคชันที่มีหลายหน้าจอ เราจะครอบคลุมทั้งการใช้งาน Named Routes แบบดั้งเดิม และ Navigator 2.0 ซึ่งเป็นวิธีที่ยืดหยุ่นและมีประสิทธิภาพมากขึ้นในการจัดการเส้นทางการนำทาง

ทำไมต้อง Navigation?

ลองนึกภาพแอปพลิเคชันที่ผู้ใช้สามารถไปยังส่วนต่างๆ ได้อย่างอิสระ เช่น หน้าหลัก, หน้าโปรไฟล์, หน้าการตั้งค่า, และอื่นๆ การนำทางคือกลไกที่ช่วยให้ผู้ใช้สามารถเคลื่อนที่ระหว่างหน้าจอเหล่านี้ได้อย่างราบรื่นและเป็นระบบ

พื้นฐานการนำทาง: Navigator.push และ Navigator.pop

ก่อนที่เราจะไปถึง Named Routes และ Navigator 2.0 มาทบทวนพื้นฐานของการนำทางใน Flutter กันก่อน Flutter มี Widget ชื่อ Navigator ที่จัดการ Stack ของ Routes (หน้าจอ) การนำทางหลักๆ จะใช้สองฟังก์ชัน:

  • Navigator.push(context, route): เพิ่ม Route ใหม่ลงใน Stack และแสดงผลบนหน้าจอ
  • Navigator.pop(context): นำ Route บนสุดออกจาก Stack และกลับไปยัง Route ก่อนหน้า

ตัวอย่างการใช้งาน:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: FirstScreen(),
  ));
}

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go to Second Screen'),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondScreen()),
            );
          },
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go back!'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}

ในตัวอย่างนี้ Navigator.push ใช้ MaterialPageRoute ซึ่งสร้าง Route ที่มีการ Transition แบบ Material Design เมื่อกดปุ่ม "Go to Second Screen" หน้าจอที่สองจะถูกแสดงผล และเมื่อกดปุ่ม "Go back!" Navigator.pop จะนำหน้าจอที่สองออกจาก Stack และกลับไปยังหน้าจอแรก

Named Routes: การนำทางด้วยชื่อ

การใช้ MaterialPageRoute โดยตรงอาจทำให้โค้ดดูซับซ้อนและยากต่อการบำรุงรักษาเมื่อแอปพลิเคชันมีขนาดใหญ่ขึ้น Named Routes ช่วยให้เราสามารถกำหนดชื่อให้กับแต่ละ Route และใช้ชื่อเหล่านั้นในการนำทางได้ ทำให้โค้ดดูสะอาดและง่ายต่อการจัดการมากขึ้น

วิธีการใช้งาน Named Routes:

  1. กำหนด Routes ใน MaterialApp:
MaterialApp(
  initialRoute: '/', // หน้าจอเริ่มต้น
  routes: {
    '/': (context) => FirstScreen(), // Route สำหรับหน้าจอแรก
    '/second': (context) => SecondScreen(), // Route สำหรับหน้าจอที่สอง
  },
)
  1. ใช้ Navigator.pushNamed เพื่อนำทางไปยัง Route ที่ต้องการ:
Navigator.pushNamed(context, '/second');
  1. ใช้ Navigator.pop เหมือนเดิมเพื่อกลับไปยัง Route ก่อนหน้า:
Navigator.pop(context);

ตัวอย่างการใช้งาน Named Routes:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    initialRoute: '/',
    routes: {
      '/': (context) => FirstScreen(),
      '/second': (context) => SecondScreen(),
    },
  ));
}

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go to Second Screen'),
          onPressed: () {
            Navigator.pushNamed(context, '/second');
          },
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go back!'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}

Named Routes ทำให้โค้ดของเราอ่านง่ายขึ้น และช่วยให้เราสามารถแก้ไขเปลี่ยนแปลง Route ได้ง่ายโดยไม่ต้องแก้ไขโค้ดในหลายๆ จุด

Navigator 2.0: การควบคุม Navigation ที่ละเอียดกว่า

Navigator 2.0 เป็น API ใหม่ที่ให้ความยืดหยุ่นและควบคุมการนำทางได้มากขึ้น เหมาะสำหรับแอปพลิเคชันที่มีโครงสร้างการนำทางที่ซับซ้อน เช่น การจัดการ Deep Linking, การจัดการ Authentication Flow, หรือการสร้าง Custom Navigation Logic

Navigator 2.0 ประกอบด้วยส่วนประกอบหลัก 3 ส่วน:

  • RouterDelegate: เป็นตัวจัดการ Stack ของ Pages (เทียบเท่ากับ Routes ใน Navigator 1.0) และแจ้งให้ Navigator ทราบเมื่อ Stack มีการเปลี่ยนแปลง
  • RouteInformationParser: แปลง RouteInformation (URL) เป็นข้อมูลที่ RouterDelegate สามารถนำไปใช้ในการสร้าง Stack ของ Pages ได้
  • BackButtonDispatcher: จัดการการกดปุ่ม Back ของระบบ (Android)

RouterDelegate:

RouterDelegate เป็นหัวใจหลักของ Navigator 2.0 มีหน้าที่:

  • จัดการ List ของ Page ซึ่งแสดงถึงหน้าจอที่อยู่ใน Navigation Stack
  • สร้าง Navigator Widget ที่ใช้ Page เหล่านั้น
  • ตอบสนองต่อการเปลี่ยนแปลงของ Route โดยการอัปเดต List ของ Page
  • แจ้ง Navigator เมื่อมีการเปลี่ยนแปลงเกิดขึ้น (ผ่าน notifyListeners())

RouteInformationParser:

RouteInformationParser ทำหน้าที่แปลง RouteInformation (ซึ่งมักจะมาจาก URL หรือ Deep Link) เป็นข้อมูลที่ RouterDelegate สามารถนำไปใช้ได้ โดยทั่วไปจะแปลงเป็น Object ที่แสดงถึง State ของแอปพลิเคชัน

ตัวอย่างการใช้งาน Navigator 2.0:

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final MyRouterDelegate _routerDelegate = MyRouterDelegate();
  final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: _routerDelegate,
      routeInformationParser: _routeInformationParser,
      backButtonDispatcher: RootBackButtonDispatcher(),
    );
  }
}

class MyRouterDelegate extends RouterDelegate<MyRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<MyRoutePath> {
  bool showSecondPage = false;

  @override
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  @override
  MyRoutePath get currentConfiguration {
    return showSecondPage ? MyRoutePath.second() : MyRoutePath.home();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        MaterialPage(
          key: ValueKey('FirstPage'),
          child: FirstScreen(onGoToSecond: () {
            showSecondPage = true;
            notifyListeners();
          }),
        ),
        if (showSecondPage)
          MaterialPage(
            key: ValueKey('SecondPage'),
            child: SecondScreen(onGoBack: () {
              showSecondPage = false;
              notifyListeners();
            }),
          ),
      ],
      onPopPage: (route, result) {
        if (!route.didPop(result)) {
          return false;
        }
        showSecondPage = false;
        notifyListeners();
        return true;
      },
    );
  }

  @override
  Future<void> setNewRoutePath(MyRoutePath configuration) async {
    if (configuration.isSecondPage) {
      showSecondPage = true;
    } else {
      showSecondPage = false;
    }
    notifyListeners();
  }
}

class MyRouteInformationParser extends RouteInformationParser<MyRoutePath> {
  @override
  Future<MyRoutePath> parseRouteInformation(RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.uri.toString());
    if (uri.pathSegments.length == 0) {
      return MyRoutePath.home();
    } else if (uri.pathSegments.first == 'second') {
      return MyRoutePath.second();
    } else {
      return MyRoutePath.home();
    }
  }

  @override
  RouteInformation restoreRouteInformation(MyRoutePath configuration) {
    if (configuration.isSecondPage) {
      return RouteInformation(uri: Uri.parse('/second'));
    }
    return RouteInformation(uri: Uri.parse('/'));
  }
}

class MyRoutePath {
  final bool isSecondPage;

  MyRoutePath.home() : isSecondPage = false;
  MyRoutePath.second() : isSecondPage = true;
}

class FirstScreen extends StatelessWidget {
  final VoidCallback onGoToSecond;

  const FirstScreen({Key? key, required this.onGoToSecond}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go to Second Screen'),
          onPressed: onGoToSecond,
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  final VoidCallback onGoBack;

  const SecondScreen({Key? key, required this.onGoBack}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go back!'),
          onPressed: onGoBack,
        ),
      ),
    );
  }
}

โค้ดนี้แสดงตัวอย่างพื้นฐานของ Navigator 2.0 โดยมี MyRouterDelegate ที่จัดการ Stack ของ Pages และ MyRouteInformationParser ที่แปลง URL เป็นข้อมูล Route อย่างไรก็ตาม การใช้งาน Navigator 2.0 จริงๆ อาจซับซ้อนกว่านี้ขึ้นอยู่กับความต้องการของแอปพลิเคชัน

สรุป

ในบทนี้ เราได้เรียนรู้เกี่ยวกับการจัดการ Navigation ใน Flutter ทั้งการใช้ Named Routes ซึ่งเป็นวิธีที่ง่ายและสะดวกในการจัดการเส้นทางการนำทาง และ Navigator 2.0 ซึ่งเป็น API ที่ยืดหยุ่นและควบคุมได้มากขึ้น เหมาะสำหรับแอปพลิเคชันที่มีโครงสร้างการนำทางที่ซับซ้อน การเลือกใช้วิธีการนำทางที่เหมาะสมขึ้นอยู่กับความต้องการและความซับซ้อนของแอปพลิเคชันของคุณ

ในบทถัดไป เราจะเรียนรู้เกี่ยวกับการใช้ HTTP Request เพื่อดึงข้อมูลจาก API ซึ่งเป็นสิ่งจำเป็นสำหรับการสร้างแอปพลิเคชันที่เชื่อมต่อกับโลกภายนอก

Office.in.th Custom Service

Want to build or adapt a system from this structure?

Office.in.th designs and develops enterprise web applications, API integrations, and public- and private-sector management systems, with long-term support.

See our software development service