การจัดการ 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:
- กำหนด Routes ใน
MaterialApp:
MaterialApp(
initialRoute: '/', // หน้าจอเริ่มต้น
routes: {
'/': (context) => FirstScreen(), // Route สำหรับหน้าจอแรก
'/second': (context) => SecondScreen(), // Route สำหรับหน้าจอที่สอง
},
)
- ใช้
Navigator.pushNamedเพื่อนำทางไปยัง Route ที่ต้องการ:
Navigator.pushNamed(context, '/second');
- ใช้
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 - สร้าง
NavigatorWidget ที่ใช้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 ซึ่งเป็นสิ่งจำเป็นสำหรับการสร้างแอปพลิเคชันที่เชื่อมต่อกับโลกภายนอก