---
title: "Navigation 2.0 - Routing On Flutter Web"
description: "Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách Navigation 2.0 hoạt động với Web Flutter, cách chúng ta có thể xây dựng các trang web và đồng bộ hóa URL với các dự án Flutter Web của chúng ta."
canonical: "https://200lab.io/blog/navigation-2-0-routing-on-flutter-web"
published: "2021-11-28T14:08:00Z"
updated: "2026-08-21T07:17:50Z"
authors: ["Chau Le"]
tags: ["Flutter", "Dart", "Navigation"]
reading_minutes: 9
access: "public"
---

Tìm hiểu cách đồng bộ hóa URL web với các trang Flutter và xử lý điều hướng trên web bằng Flutter

Trong bài viết này, chúng ta sẽ tìm hiểu cách Navigation 2.0 hoạt động với Web Flutter, cách xây dựng các trang web và đồng bộ hóa URL với các dự án Flutter Web của chúng ta.

Trước đây, với các kỹ thuật điều hướng bắt buộc, chúng ta chỉ có thể push và pop các route trong stack điều hướng, nhưng điều đó không xử lý URL web và lịch sử web. Vì vậy, đội ngũ Flutter đã đưa ra kỹ thuật điều hướng khai báo mới này để xử lý các URL như một phần không thể thiếu của route management.

Hãy tìm hiểu các kỹ thuật khác nhau mà chúng ta có thể sử dụng để quản lý các Route và cách Navigation 2.0 làm cho nó tốt hơn.

## 1. Routing using OnGenerateRoute

Trước tiên, hãy bắt đầu với Routing bằng cách sử dụng onGenerateRoute. Nếu bạn thử điều hướng đến các trang khác nhau bằng cách sử dụng`Navigator.push()`đơn giản hoặc các phương pháp khác, trang sẽ thay đổi nhưng URL thì không. Ngoài ra, sẽ không có lịch sử (history, trang trước đó) nào được lưu lại.

Chúng ta có thể làm điều này bằng cách sử dụng Generate Routes.

`onGenerateRoutes`là lệnh callback của route generator được sử dụng khi ứng dụng được điều hướng đến một route đã đặt tên (named route). Khi sử dụng lệnh này, chúng ta sẽ tạo các route, điều hướng đến trang khác và đồng bộ hóa các thay đổi với URL của trình duyệt.

Chúng ta cần hai tệp route (cho code rõ ràng hơn hơn), một tệp để xác định tên route và tệp khác để xác định`onGenerateRoute`class.

Tệp`routes_name`sẽ có các route với tên route tương ứng.

```
class RoutesName {
  // ignore: non_constant_identifier_names
  static const String FIRST_PAGE = '/first_page';
  // ignore: non_constant_identifier_names
  static const String SECOND_PAGE = '/second_page';
}
```

Ở đây, chúng ta có hai trang,`FIRST_PAGE`(Ví dụ: localhost: 3000 / # / first\_page) và`SECOND_PAGE`(Ví dụ: localhost: 3000 / # / second\_page).

Bây giờ, chúng ta sẽ tạo một`onGenerateRoute`class có thể tái sử dụng trong tệp route thứ hai của chúng ta.

```
class RouteGenerator {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case RoutesName.FIRST_PAGE:
        return _GeneratePageRoute(
            widget: FirstPage(), routeName: settings.name);
      case RoutesName.SECOND_PAGE:
        return _GeneratePageRoute(
            widget: SecondPage(), routeName: settings.name);
      default:
        return _GeneratePageRoute(
            widget: FirstPage(), routeName: settings.name);
    }
  }
```

Sử dụng route settings, chúng ta sẽ nhận được tên Route và sau đó điều hướng đến trang tương ứng. Ở đây,  `_GeneratePageRoute`là class mở rộng `PageRouteBuilder`. Nó được sử dụng để thêm các hoạt ảnh chuyển đổi điều hướng.

```
class _GeneratePageRoute extends PageRouteBuilder {
  final Widget widget;
  final String routeName;
  _GeneratePageRoute({this.widget, this.routeName})
      : super(
            settings: RouteSettings(name: routeName),
            pageBuilder: (BuildContext context, Animation<double> animation,
                Animation<double> secondaryAnimation) {
              return widget;
            },
            transitionDuration: Duration(milliseconds: 500),
            transitionsBuilder: (BuildContext context,
                Animation<double> animation,
                Animation<double> secondaryAnimation,
                Widget child) {
              return SlideTransition(
                textDirection: TextDirection.rtl,
                position: Tween<Offset>(
                  begin: Offset(1.0, 0.0),
                  end: Offset.zero,
                ).animate(animation),
                child: child,
              );
            });
}
```

Việc thiết lập các route gần như đã hoàn thành. Bước cuối cùng là thêm`generateRoute`vào tệp`main.dart`của chúng ta.

```
void main() {
  runApp(App());
}
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      builder: (context, child) => HomePage(child: child),
      onGenerateRoute: RouteGenerator.generateRoute,
      initialRoute: RoutesName.FIRST_PAGE,
    );
  }
}
```

Bên trong`MaterialApp`, chúng ta sẽ thêm các route vào`onGenerateRoute`property và đặt một initial route (Trang này sẽ được tải trước và route tương ứng sẽ là initial route được hiển thị trên URL). Bây giờ, bằng cách sử dụng builder property của ứng dụng material, chúng ta sẽ chèn các widget.`HomePage`là một stateless widget, tạo inserted widget.

```
class HomePage extends StatelessWidget {
  final Widget child;

  const HomePage({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: child,
    );
  }
}
```

Chúng ta có hai trang cho hai route của chúng ta ( /first-page and /second-page). Để điều hướng từ trang này sang trang khác, chúng ta sẽ sử dụng`Navigator.pushNamed(context, RouteName);`

**First Page:**

```
class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 400,
      child: Column(
        children: [
          Container(
            child: Center(
              child: Text("FIRST PAGE"),
            ),
          ),
          TextButton(
              onPressed: () {

               Navigator.pushNamed(context, RoutesName.SECOND_PAGE);
              },
              child: Text("NAVIGATE")),
        ],
      ),
    );
  }
}
```

**Second Page:**

```
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 400,
      child: Column(
        children: [
          Container(
            child: Center(
              child: Text("SECOND PAGE"),
            ),
          ),
          TextButton(
              onPressed: () {
                Navigator.pop(context);
                // Navigator.pushNamed(context, RoutesName.SECOND_PAGE);
              },
              child: Text("NAVIGATE")),
        ],
      ),
    );
  }
}
```

Phần Router của chúng ta hiện đã hoàn thành.

Bây giờ, chúng ta hãy xem xét một phương pháp khác để thực hiện Routing trong Web Flutter bằng cách sử dụng Navigation 2.0.

## 2. Routing Flutter (Navigation 2.0)

Navigation 2.0 tuân theo cách tiếp cận khai báo. Sử dụng phương pháp này, chúng ta sẽ đồng bộ hóa URL web với dự án Flutter của chúng ta.

Hãy xem cách chúng ta có thể làm điều này.

![](https://assets.200lab.io/images/2024/12/05/image-168-d1cc8fe503d888f59a4c.png)

Chúng ta sẽ sử dụng một constructor có tên là`MaterialApp.router()`. Nó tạo ra một ứng dụng material sử dụng router thay vì Navigator và yêu cầu hai thứ,`routerDelegate`và`routerInformationParser`.

URL typed sẽ chuyển qua`RouterInformationParser` và nó sẽ chuyển URL thành dữ liệu do người dùng định nghĩa (user defined data). Sau đó, nó sẽ được chuyển cho`RouterDelegate`, phụ trách các state của ứng dụng router.

Trước tiên, hãy tạo kiểu dữ liệu chung của chúng ta, trong đó route sẽ được chuyển đổi thông qua`RouteInformationParser`.

```
class HomeRoutePath {
  final String pathName;
 final bool isUnkown;

  HomeRoutePath.home()
      : pathName = null,
        isUnkown = false;

  HomeRoutePath.otherPage(this.pathName) : isUnkown = false;

  HomeRoutePath.unKown()
      : pathName = null,
        isUnkown = true;

  bool get isHomePage => pathName == null;
  bool get isOtherPage => pathName != null;
}
```

Trong class trên, chúng ta có hai biến,`pathName`, sẽ là tham số URL (sau “/“) và`boolean`, sẽ hiển thị 'trang không xác định (unknown page)' trong trường hợp URL không hợp lệ.

Ngoài ra, chúng ta đã tạo ra thêm một số named constructors.

`home()`hiển thị initial screen khi route là “/“. Trong trường hợp này, boolean không xác định sẽ là false và đường dẫn sẽ trống.`otherPage()`dành cho các trang`pathName`. Ví dụ. /xyz.`unknown()`dành cho các đường dẫn không xác định.

Sau đó, chúng ta sẽ tạo`RouteInformationParser`chuyển đổi URL thành`HomeRoutePath`của chúng ta.

```
class HomeRouteInformationParser extends RouteInformationParser<HomeRoutePath> {
  @override
  Future<HomeRoutePath> parseRouteInformation(
      RouteInformation routeInformation) async {
       final uri = Uri.parse(routeInformation.location);

    if (uri.pathSegments.length == 0) {
      return HomeRoutePath.home();
    }

    if (uri.pathSegments.length == 1) {
      final pathName = uri.pathSegments.elementAt(0).toString();
      if (pathName == null) return HomeRoutePath.unKown();
      return HomeRoutePath.otherPage(pathName);
    }

    return HomeRoutePath.unKown();
  }

  @override
  RouteInformation restoreRouteInformation(HomeRoutePath homeRoutePath) {
    if (homeRoutePath.isUnkown) return RouteInformation(location: '/error');
    if (homeRoutePath.isHomePage) return RouteInformation(location: '/');
    if (homeRoutePath.isOtherPage)
      return RouteInformation(location: '/${homeRoutePath.pathName}');

    return null;
  }
}
```

Chúng ta sẽ mở rộng`RouterInformationParser`với type`HomeRoutePath`và sau đó chúng ta sẽ override`parseRouteInformation`method để chịu trách nhiệm phân tích (parsing) cú pháp đường dẫn URL thành`HomeRoutePath`.

Khi chúng ta không có phân đoạn đường dẫn (zero path segments) (pathSegements sau “/“ ví dụ/abc/xyz có 2 phân đoạn đường dẫn), chúng ta sẽ chuyển đổi thành`home()`type. Tương tự, chúng ta sẽ chuyển đổi sang các type khác tùy theo điều kiện.

Ngoài ra, chúng ta có một method khác để override, được gọi là`restoreRouteInformation`. Method này sẽ lưu trữ lịch sử duyệt web trên trình duyệt. Ở đây, chúng ta đã chuyển đường dẫn đến`RouteInformation`theo`HomeRoutePath`state.

Bây giờ chúng ta đã hoàn thành`informationParser`.

Hãy chuyển sang`RouterDelegate`.

Class này cần mở rộng`RouterDelegate`với type`HomeRoutePat`. Nó có 5 override method và chúng ta không cần phải xử lý tất cả chúng.`addListener`method và`removeListener`method, chúng ta có thể sử dụng`ChangeNotifier`và thêm nó vào function.

Ngoài ra đối với`popRoute`, chúng ta có thể sử dụng mixin`PopNavigatorRouterDelegateMixin`.

```
class HomeRouterDelegate extends RouterDelegate<HomeRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<HomeRoutePath> {
  String pathName;
  bool isError = false;

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

  @override
  HomeRoutePath get currentConfiguration {
    if (isError) return HomeRoutePath.unKown();

    if (pathName == null) return HomeRoutePath.home();

    return HomeRoutePath.otherPage(pathName);
  }

  void onTapped(String path) {
    pathName = path;
    print(pathName);
    notifyListeners();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
        key: navigatorKey,
        pages: [
          MaterialPage(
            key: ValueKey('HomePage'),
            child: HomePage(
              onTapped: (String path) {
                pathName = path;
                notifyListeners();
              },
            ),
          ),
          if (isError)
            MaterialPage(key: ValueKey('UnknownPage'), child: UnkownPage())
          else if (pathName != null)
            MaterialPage(
                key: ValueKey(pathName),
                child: PageHandle(
                  pathName: pathName,
                ))
        ],
        onPopPage: (route, result) {
          if (!route.didPop(result)) return false;

          pathName = null;
          isError = false;
          notifyListeners();

          return true;
        });
  }

  @override
  Future<void> setNewRoutePath(HomeRoutePath homeRoutePath) async {
    if (homeRoutePath.isUnkown) {
      pathName = null;
      isError = true;
      return;
    }

    if (homeRoutePath.isOtherPage) {
      if (homeRoutePath.pathName != null) {
        pathName = homeRoutePath.pathName;
        isError = false;
        return;
      } else {
        isError = true;
        return;
      }
    } else {
      pathName = null;
    }
  }
}
```

Ở đây, chúng ta đã xác định hai state mà chúng ta sẽ sử dụng để điều hướng đến các trang. Một là`pathName`và một là cho các trang lỗi`isError`.

Hãy bắt đầu với từng override method:

`currentConfiguration`: được gọi bởi \[Router\] khi nó phát hiện thông tin route có thể đã thay đổi do rebuild. Vì vậy, theo các điều kiện, chúng ta đang call các`HomePageRoute`constructors.`setNewRoutePath`: Phương thức này xử lý việc định tuyến khi người dùng nhập URL vào trình duyệt và nhấn enter.`build`: Trả về Navigator với`HomePage`của chúng ta theo mặc định và thay đổi state thông qua`HomePage`và nếu`pathName` không rỗng, chúng ta có thể map tên với các trang và hiển thị các trang khác nhau theo các route khác nhau (ở đây mình chỉ hiển thị một trang có tên route).

Bước cuối cùng là thêm`routerDelegate`và`routerInformationParser` của chúng ta vào`MaterialApp.router()`bên trong`main.dart`.

```
void main() {
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: "Flutter Navigaton 2.0",
      routerDelegate: HomeRouterDelegate(),
      routeInformationParser: HomeRouteInformationParser(),
    );
  }
}
```

Kết luận

Phương pháp liên quan đến Navigation 2.0 có quá nhiều code soạn sẵn để thêm vào nhưng nó xử lý các trường hợp bị bỏ sót trong`onGenerateRoute`. Bạn có thể nhận thấy rằng mũi tên chuyển tiếp trong trình duyệt có thể không hoạt động đúng với cách tiếp cận đầu tiên. Tuy nhiên, cách tiếp cận đầu tiên có phần dễ thực hiện và dễ quản lý hơn so với cách thứ hai.

Bài viết được lược dịch từ [Vishal Singh](<https://techblog.geekyants.com/navigation-20-routing-on-flutter-web>).
