, June 27, 2022

0 kết quả được tìm thấy

Navigation 2.0 - Routing On Flutter Web

  • Đăng bởi  Chau Le
  •  Nov 28, 2021

  •   8 min reads
Navigation 2.0 - Routing On Flutter Web

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ụngNavigator.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.

onGenerateRouteslà 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 địnhonGenerateRouteclass.

Tệproutes_namesẽ 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ộtonGenerateRouteclass 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,  _GeneratePageRoutelà 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êmgenerateRoutevào tệpmain.dartcủ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 trongMaterialApp, chúng ta sẽ thêm các route vàoonGenerateRouteproperty 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.HomePagelà 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ụngNavigator.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.

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ứ,routerDelegaterouterInformationParser.

URL typed sẽ chuyển quaRouterInformationParser 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 choRouterDelegate, 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 quaRouteInformationParser.

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 trangpathName. Ví dụ. /xyz.unknown()dành cho các đường dẫn không xác định.

Sau đó, chúng ta sẽ tạoRouteInformationParserchuyển đổi URL thànhHomeRoutePathcủ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ộngRouterInformationParservới typeHomeRoutePathvà sau đó chúng ta sẽ overrideparseRouteInformationmethod để chịu trách nhiệm phân tích (parsing) cú pháp đường dẫn URL thànhHomeRoutePath.

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ànhhome()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 đếnRouteInformationtheoHomeRoutePathstate.

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

Hãy chuyển sangRouterDelegate.

Class này cần mở rộngRouterDelegatevới typeHomeRoutePat. Nó có 5 override method và chúng ta không cần phải xử lý tất cả chúng.addListenermethod vàremoveListenermethod, chúng ta có thể sử dụngChangeNotifiervà thêm nó vào function.

Ngoài ra đối vớipopRoute, chúng ta có thể sử dụng mixinPopNavigatorRouterDelegateMixin.

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àpathNamevà một là cho các trang lỗiisError.

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ácHomePageRouteconstructors.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ớiHomePagecủa chúng ta theo mặc định và thay đổi state thông quaHomePagevà nếupathName 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êmrouterDelegaterouterInformationParser của chúng ta vàoMaterialApp.router()bên trongmain.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 trongonGenerateRoute. 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.

Bài viết liên quan

Tổng hợp các Shortcuts, Extensions & Settings trong VSCode khi lập trình Flutter

Trong bài viết này, mình sẽ liệt kê cho bạn các shortcuts, extensions, setting mà bạn nên sử dụng để lập trình Flutter hàng ngày....

Tổng hợp các Shortcuts, Extensions & Settings trong VSCode khi lập trình Flutter
[Phần 1] Những extension cần thiết khi làm việc với Flutter trên VS Code

VS Code là 1 text editor tuyệt vời được phát triển bởi Mircosoft. Đây là một trong các công cụ được developer trên toàn thế giới yêu thích vì sự tiện lợi, dễ dùng và nó chứa hàng ngàn, thậm chí trăm ngàn các extension phục vụ cho bạn trong quá trình bạn phát triển phần mềm....

[Phần 1] Những extension cần thiết khi làm việc với Flutter trên VS Code
Fix lỗi Flutter 3 không thể build app trên iOS

Cách fix lỗi Flutter 3 không thể build và run được app trên iOS với video hướng dẫn chia tiết...

Fix lỗi Flutter 3 không thể build app trên iOS
Flutter Coding UI Speed Code

Nhận bản UI siêu đẹp nhưng làm sao để phân tích rồi code ra một cách chính xác nhất? Series này 200Lab sẽ cho bạn một góc nhìn thực tế về quá trình code UI cho app Movie Ticket....

Flutter Coding UI Speed Code
Flutter 3 - Những cập nhật mới có gì hot

Bài viết này cung cấp cho bạn những thông tin cập nhật mới nhất của Flutter 3, giúp bạn có cái nhìn tổng quan về các thay đổi và tính năng bổ ích...

Flutter 3 - Những cập nhật mới có gì hot
You've successfully subscribed to 200Lab Blog
Great! Next, complete checkout for full access to 200Lab Blog
Xin chào mừng bạn đã quay trở lại
OK! Tài khoản của bạn đã kích hoạt thành công.
Success! Your billing info is updated.
Billing info update failed.
Your link has expired.